)]}'
{
  "log": [
    {
      "commit": "604accad567eb6e7fb4c7fdc422470c25f35f467",
      "tree": "7430e3868ca794bffd3aa6f5dd58ee0c95c90b6d",
      "parents": [
        "6e1279cec6f6721115de82ae1606530dd0c771b0"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 12:38:42 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 12:38:42 2026 +0100"
      },
      "message": "strip the new mbox files\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "6e1279cec6f6721115de82ae1606530dd0c771b0",
      "tree": "1287f57a1ec5e24a57a74c4ca9fb1c9ea03e4d04",
      "parents": [
        "0d2759b58d1a7b83e35218c9d395f1a55b176511"
      ],
      "author": {
        "name": "GONG Ruiqi",
        "email": "gongruiqi1@huawei.com",
        "time": "Wed Oct 28 19:21:36 2026 +0800"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 12:38:23 2026 +0100"
      },
      "message": "proposed: Add Ruiqi\u0027s results for v7.2.13\n\nSigned-off-by: GONG Ruiqi \u003cgongruiqi1@huawei.com\u003e\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "0d2759b58d1a7b83e35218c9d395f1a55b176511",
      "tree": "54e375caef41a17ccccabc6e545ca1e103749730",
      "parents": [
        "37f6ff221864caafd661ce64c3c8644f1c2dcfd2"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 12:36:32 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 12:36:32 2026 +0100"
      },
      "message": "add some 7.2.13 cve ids\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "37f6ff221864caafd661ce64c3c8644f1c2dcfd2",
      "tree": "5d12e32146680cf04aaf2b3da548a68fa86c5420",
      "parents": [
        "2e146ea926d36019b15f70c11d4dcb4fc13cf5f7"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 11:32:24 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 11:32:24 2026 +0100"
      },
      "message": "add 7.2.13 review from greg\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "2e146ea926d36019b15f70c11d4dcb4fc13cf5f7",
      "tree": "296cb1a8d3894334e1efcc19e1de03fac0d18a77",
      "parents": [
        "5c0ef16c14bb096b836609515ac7f5131bc26264"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 08:25:01 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 08:25:01 2026 +0100"
      },
      "message": "mark 7.2.12 reviews as done, again.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "5c0ef16c14bb096b836609515ac7f5131bc26264",
      "tree": "132c40ac8149710f9eea6a07b3746cb1fa0afbfc",
      "parents": [
        "60145214bdfed6b5018c8556bff8911c260dcba0"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 08:17:10 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 08:17:10 2026 +0100"
      },
      "message": "strip the new mbox file\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "60145214bdfed6b5018c8556bff8911c260dcba0",
      "tree": "305dd636c1f4c4b40c8ea6ceedfc3c3b4a44d1f3",
      "parents": [
        "457afc5a7e6c50c13f8fa8ed70ba826c2b3838aa"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 04:55:51 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Wed Oct 28 04:55:51 2026 +0100"
      },
      "message": "assign a cve id on request\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "457afc5a7e6c50c13f8fa8ed70ba826c2b3838aa",
      "tree": "b62623ab1b8eca7a9534c74a795ce3b12fbe0b5d",
      "parents": [
        "049be51b62c4841bf5c33131869af5e246918c8b"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Tue Oct 27 16:53:53 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Tue Oct 27 16:53:53 2026 +0100"
      },
      "message": "updates based on new .vulnerable file\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "049be51b62c4841bf5c33131869af5e246918c8b",
      "tree": "046d3562ba2b3c52d2b38c385f5f85e647e26f64",
      "parents": [
        "a885cfdfa3c0faa000023e4b0a37f2fd9e0aec76"
      ],
      "author": {
        "name": "Miguel Gazquez (Schneider Electric)",
        "email": "miguel.gazquez@bootlin.com",
        "time": "Tue Oct 27 11:35:37 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Tue Oct 27 16:53:27 2026 +0100"
      },
      "message": "CVE-2024-38564: update .vulnerable file\n\nBefore 3505cb9fa26cfec9512744466e754a8cbc2365b0 (\"bpf: Add attach_type\nchecks under bpf_prog_attach_check_attach_type\"), the function\nlink_create did the check that is added back in the patch fixing the\nCVE, so we are not vulnerable before this commit.\n\nUpdate the .vulnerable file accordingly.\n\nReported-By: Sasha Levin \u003csashal@kernel.org\u003e\nSigned-off-by: Miguel Gazquez (Schneider Electric) \u003cmiguel.gazquez@bootlin.com\u003e\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "a885cfdfa3c0faa000023e4b0a37f2fd9e0aec76",
      "tree": "4ec4a14df3808d268a2f7cbf35da9ac046e4a3b4",
      "parents": [
        "265c5f93f45b44b2a1f4b4dc3ceddedfec777608"
      ],
      "author": {
        "name": "Allen Pais",
        "email": "allen.lkml@gmail.com",
        "time": "Tue Oct 27 14:57:08 2026 +0000"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Tue Oct 27 16:53:16 2026 +0100"
      },
      "message": "proposed: Add Allen\u0027s v7.2.[12/13] results\n\nSigned-off-by: Allen Pais \u003capais@linux.microsoft.com\u003e\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "265c5f93f45b44b2a1f4b4dc3ceddedfec777608",
      "tree": "7a937de4baea07af0359296fc676534bab0e0a0c",
      "parents": [
        "da512fd601a2522869f3f4feb492a61086a78b32"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Tue Oct 27 16:53:04 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Tue Oct 27 16:53:04 2026 +0100"
      },
      "message": "updates based on new .vulnerable file\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "da512fd601a2522869f3f4feb492a61086a78b32",
      "tree": "19b7c134ecbcdc5a8f3bb5d0aab9ed63cf9d46c9",
      "parents": [
        "718933ac98a3bc8a577b7273c62bfa5a7d2e33a5"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Tue Oct 27 10:26:15 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Tue Oct 27 10:26:15 2026 -0400"
      },
      "message": "CVE-2026-99090: Add .vulnerable file\n\nThe root cause is d38ceaf99ed01 (\"drm/amdgpu: add core driver (v4)\"),\nby Alex Deucher, dated 2015-04-20, first in v4.2-rc1. That commit\ncreated amdgpu_get_vblank_counter_kms() with an MMIO read of the CRTC\nframe counter and no synchronization against GPU reset. The fix adds\nthat synchronization to this same function.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n1a6023edfbb5 (v7.3-rc7, cherry-pick of 51a4359f7431, Cc: stable, no\nFixes: tag) edits only amdgpu_get_vblank_counter_kms() in amdgpu_kms.c:\n\n    +\tif (!down_read_trylock(\u0026adev-\u003ereset_domain-\u003esem))\n    +\t\treturn drm_crtc_vblank_count(crtc);\n     \t...  /* hw counter + scanoutpos read loop */\n    +\tup_read(\u0026adev-\u003ereset_domain-\u003esem);\n     \treturn count;\n\nBefore the fix, the function did two things with no lock held:\n\n - Counter read: amdgpu_display_vblank_get_counter(), which is\n   mode_info.funcs-\u003evblank_get_counter. On the old DCE code that is\nRREG32(mmCRTC_STATUS_FRAME_COUNT...). On DC it is\ndm_vblank_get_counter() -\u003e dc_stream_get_vblank_counter() -\u003e\ntg-\u003efuncs-\u003eget_frame_count.\n\n - Scanout position: amdgpu_display_get_crtc_scanoutpos(), called in a\n   retry loop.\n\nAfter the fix it holds the reset semaphore\u0027s read side across both\nreads. If a reset holds the write side, it returns DRM\u0027s cached software\ncount and touches no hardware.\n\n2. How the bug shows up today\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nReset thread:\n\n 1. amdgpu_device_gpu_recover() calls\n    amdgpu_device_lock_reset_domain(), which does\n    down_write(\u0026reset_domain-\u003esem) (amdgpu_reset.c:314).\n 2. It then suspends the IP blocks. That includes dm_suspend() in\n    in-reset mode, which takes dc_lock and calls\n    amdgpu_dm_commit_zero_streams().\n 3. Then come the ASIC reset and resume.\n\nAnother task running at the same time:\n\n - The atomic commit worker (amdgpu_dm_atomic_commit_tail() -\u003e\n   amdgpu_dm_commit_planes()) calls amdgpu_get_vblank_counter_kms(pcrtc)\n   directly in its flip-throttle code (amdgpu_dm.c:~4116/4144). That\n   code runs before dc_lock is taken.\n - DRM core also calls it through crtc-\u003efuncs-\u003eget_vblank_counter\n(amdgpu_dm_crtc.c:593), from drm_crtc_vblank_get(),\ndrm_crtc_vblank_on() and similar.\n\nWhat happens on that second task:\n\n 1. The read goes down to amdgpu_device_rreg() and then\n    amdgpu_device_skip_hw_access().\n 2. With lockdep enabled, in_task() is true, so it tries\n    down_read_trylock(\u0026reset_domain-\u003esem).\n 3. The trylock fails because the reset thread holds the write side.\n 4. lockdep_assert_held() then fires, because this task holds neither\n    side. That produces a WARN and TAINT_WARN, or a panic with\n    panic_on_warn.\n\nWithout lockdep the same thing is silent. The task reads display MMIO\nwhile the display block is being torn down and the ASIC reset. That\ngives a bogus counter, and the commit that added the lockdep check\ndescribes the general result as \"system randomly hang\".\n\n3. History of the function \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on amdgpu_get_vblank_counter_kms in origin/master shows\nthese commits:\n\n - d38ceaf99ed01 (\"drm/amdgpu: add core driver (v4)\"), v4.2-rc1:\n   Created the function. It did a bare\n\"return amdgpu_display_vblank_get_counter(adev, crtc);\" with no lock,\nwired up as drm_driver.get_vblank_counter (amdgpu_drv.c).\n\n - 88e72717c2de4, v4.4:\n   \"int crtc\" became \"unsigned int pipe\". Mechanical.\n\n - 8e36f9d33c134 (\"Fixup hw vblank counter/ts\"), v4.4-rc4:\n   Added the scanoutpos read and the retry loop. More unprotected\n   MMIO, same flaw.\n\n - aa8e286a8af9f, v4.16:\n   Rename of the scanoutpos helper.\n\n - e3eff4b5d91e0 (\"Convert to CRTC VBLANK callbacks\"), v5.7:\nSignature changed to (struct drm_crtc *) and the function was wired\ninto the DCE and DM crtc_funcs. Same body.\n\n - 1348969ab68cb, v5.10:\n   drm_to_adev() conversion.\n\n - 1a6023edfbb56, v7.3-rc7:\n   The fix.\n\nNo commit between d38ceaf99ed01 and the fix ever made this function\nsynchronize with reset.\n\nWhy d38ceaf99ed01 already had the race\n--------------------------------------\n\nIn that commit, amdgpu_gpu_reset() did\ndown_write(\u0026adev-\u003eexclusive_lock).\nThat rwsem was the reset lock of the time, and it is the ancestor of\nreset_domain-\u003esem. Under it, the reset called amdgpu_suspend() on every\nIP block, including the display block, then amdgpu_asic_reset() and\namdgpu_resume().\n\nThe page-flip worker in that same commit did take\ndown_read(\u0026adev-\u003eexclusive_lock) around its hardware work. So the rule\n\"take the reset lock for read before touching hardware\" already\nexisted. The vblank counter callback never followed it. It read\nCRTC_STATUS_FRAME_COUNT with nothing preventing a concurrent reset of\nthe display engine. That is the exact defect the fix closes.\n\nThe reset lock changed names, not purpose\n-----------------------------------------\n\n - exclusive_lock was removed in 0c418f10104d4 (v4.3). That removal\n   didn\u0027t affect the vblank counter, which never took it.\n - adev-\u003elock_reset mutex arrived in 13a752e3a2464 (2017).\n - It became the adev-\u003ereset_sem rwsem in 6049db43d6dd9 (2020).\n - It moved to reset_domain-\u003esem in d0fb18b535679 (2022).\n\nThe vblank counter skipped this lock in every version.\n\n4. Commits that are not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 56b53c0b5aa5d (\"add codes to capture invalid hardware access when\n   recovery\") (Dennis Li, v5.13). It added\n   amdgpu_device_skip_hw_access() with the trylock plus\n   lockdep_assert_held() check. Its own message says other threads must\n   not touch hardware during a reset. It only detects the existing race\n   and turns it into the WARN/TAINT that the fix\u0027s message cites. It\n   made the bug visible; it did not cause it.\n\n - 8e36f9d33c134 made the window larger with more MMIO and a loop, but\n   the unprotected counter read was already there.\n\n - e3eff4b5d91e0, 1348969ab68cb and aa8e286a8af9f are refactors.\n\n - DC (amdgpu_dm) uses this same callback through amdgpu_dm_crtc_funcs\n   and calls it directly from the commit path. It inherited the flaw\n   rather than introducing it.\n\nConclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: d38ceaf99ed01 (\"drm/amdgpu: add core driver (v4)\"). It\n   made the vblank counter callback read display registers with no\n   synchronization against GPU reset, while reset suspended and reset\n   the whole ASIC including the display engine.\n - Suggested tag:\n   Fixes: d38ceaf99ed0 (\"drm/amdgpu: add core driver (v4)\")\n - Affected range: every kernel since v4.2.\n - Visibility: the lockdep WARN/TAINT_WARN described in the fix appears\n   only from v5.13 (56b53c0b5aa5d) onward.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "718933ac98a3bc8a577b7273c62bfa5a7d2e33a5",
      "tree": "d94782c135e224a4d024f3a28ffe89a7a5adef7f",
      "parents": [
        "648823485b1ae28619ef8e8bfff11db7e708a253"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Tue Oct 27 10:15:13 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Tue Oct 27 10:15:13 2026 -0400"
      },
      "message": "sasha: review v7.2.13\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "648823485b1ae28619ef8e8bfff11db7e708a253",
      "tree": "2a53e0cd7bb01451856f9affc38d99f338fc4528",
      "parents": [
        "6d22dd9347d581e08997d8ddb4d0fcde9c1d81da"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 16:17:43 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 16:17:43 2026 +0100"
      },
      "message": "updates for new stable releases\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "6d22dd9347d581e08997d8ddb4d0fcde9c1d81da",
      "tree": "681e51bd789a457cc51e34b45ef623ecef84ba78",
      "parents": [
        "ed9df6c0cded24e042da87a1153056785443691c"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:54:41 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:54:41 2026 +0100"
      },
      "message": "reject CVE-2026-98943\n\nIt was not in any released kernel versions\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "ed9df6c0cded24e042da87a1153056785443691c",
      "tree": "5dd6ae9738ee1da4379e16c17ce3198c5d94ae82",
      "parents": [
        "bc082eafb501ab67d6419189641f00db581eda7d"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:54:06 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:54:06 2026 +0100"
      },
      "message": "strip the new mbox files\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "bc082eafb501ab67d6419189641f00db581eda7d",
      "tree": "12b83fc2f39d6e9d8c4894a747c0a02ba61669c9",
      "parents": [
        "746166a12920c26658d92bf84afedfdc9ed6bd8d"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:53:45 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:53:45 2026 +0100"
      },
      "message": "mark 7.2.12 review as completed\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "746166a12920c26658d92bf84afedfdc9ed6bd8d",
      "tree": "59d5496d42a57f1491da4781afee941bf3b8e70f",
      "parents": [
        "c22b7534365133ca757649e833efdd983d1fe541"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:52:45 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:52:45 2026 +0100"
      },
      "message": "mark 7.2.11 review as completed\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "c22b7534365133ca757649e833efdd983d1fe541",
      "tree": "c61fa03896404b720eeab72266b03a2598ed869c",
      "parents": [
        "2cea9f09a1bb8f923d18bb46f18a1ce952a73bec"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:52:22 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 15:52:22 2026 +0100"
      },
      "message": "assign some more 7.2.11 cve ids\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "2cea9f09a1bb8f923d18bb46f18a1ce952a73bec",
      "tree": "ac498ed05942855dd83d1c16969409f607ad4a72",
      "parents": [
        "a40a8116783221d1a5c2d3608c08bc1d1453e74a"
      ],
      "author": {
        "name": "Lee Jones",
        "email": "lee@kernel.org",
        "time": "Mon Oct 26 12:38:32 2026 +0000"
      },
      "committer": {
        "name": "Lee Jones",
        "email": "lee@kernel.org",
        "time": "Mon Oct 26 12:38:40 2026 +0000"
      },
      "message": "proposed: Add Lee\u0027s v7.2.12 results\n\nSigned-off-by: Lee Jones \u003clee@kernel.org\u003e\n"
    },
    {
      "commit": "a40a8116783221d1a5c2d3608c08bc1d1453e74a",
      "tree": "c6ec17799b00e4ff862df31aab7ebedd2b4017e5",
      "parents": [
        "35b54ef9157ec5c51e3a2939eb8c2fc5fb60f42e"
      ],
      "author": {
        "name": "Lee Jones",
        "email": "lee@kernel.org",
        "time": "Mon Oct 26 12:38:25 2026 +0000"
      },
      "committer": {
        "name": "Lee Jones",
        "email": "lee@kernel.org",
        "time": "Mon Oct 26 12:38:40 2026 +0000"
      },
      "message": "proposed: Add Lee\u0027s v7.2.11 results\n\nSigned-off-by: Lee Jones \u003clee@kernel.org\u003e\n"
    },
    {
      "commit": "35b54ef9157ec5c51e3a2939eb8c2fc5fb60f42e",
      "tree": "167546992ff566e5b253b4b5304e150755f81254",
      "parents": [
        "445fb810182006745e17d00ff57b3cc25e55fffc"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 08:06:18 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Mon Oct 26 08:06:18 2026 +0100"
      },
      "message": "updates due to new .vulnerable file additions\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "445fb810182006745e17d00ff57b3cc25e55fffc",
      "tree": "efa0d6c02d68f44753c45eeda38d31a96b1c797b",
      "parents": [
        "dbb262f18c0ae6462161cbf3bc3241b5b2ae63e8"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 11:46:10 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-99083: Add .vulnerable file\n\nThe root cause is 98422bdd4cb3c (\"ksmbd: fix out of bounds read in\nsmb2_sess_setup\"), by Namjae Jeon, merged in v6.5-rc1 and tagged\nCc: stable. It fixed an earlier bug (ZDI-CAN-21355) and introduced this one.\n\nThe fix, 23a0be6a84a3 (\"ksmbd: fix heap out-of-bounds write in\nkrb5_authenticate()\"), has no Fixes: tag. \"Fixes: 98422bdd4cb3c\" would\nbe the correct one.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix touches one line in krb5_authenticate()\n(fs/smb/server/smb2pdu.c):\n\n    -       out_len \u003d work-\u003eresponse_sz -\n    +       out_len \u003d work-\u003eresponse_sz - work-\u003enext_smb2_rsp_hdr_off -\n                    (le16_to_cpu(rsp-\u003eSecurityBufferOffset) + 4);\n\nout_len tells ksmbd_krb5_authenticate() how much room is left after\nout_blob for the Kerberos AP-REP. That function trusts it:\n\n    if (*out_len \u003c\u003d resp-\u003espnego_blob_len)     /* the only size check */\n            return -EINVAL;\n    ...\n    memcpy(out_blob, resp-\u003epayload + resp-\u003esession_key_len,\n           resp-\u003espnego_blob_len);             /* the heap write */\n\nIf out_len is too large, the check passes and the memcpy() writes past\nthe end of work-\u003eresponse_buf.\n\n2. The buffer arithmetic (code just before the fix)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Allocation: smb2_allocate_rsp_buf() makes response_buf \u003d\n   kvzalloc(sz) and sets response_sz \u003d sz. For any compound request\n   (NextCommand \u003e 0), sz \u003d 448 + max_trans_size.\n\n - Response layout: the 4-byte RFC1002 length comes first, then the\n   compound responses packed one after another. next_smb2_rsp_hdr_off is\n   the offset of the current response, measured from response_buf + 4.\n   init_chained_smb2_rsp() advances it by\n   ALIGN(len, 8) per command.\n\n - Where rsp points: smb2_sess_setup() calls\n   WORK_BUFFERS(work, req, rsp). When next_smb2_rcv_hdr_off !\u003d 0,\n__wbuf() returns rsp \u003d ksmbd_resp_buf_next(work), which is response_buf\n+ next_smb2_rsp_hdr_off + 4.\n\nSo for a SESSION_SETUP that is not the first command in a compound:\n\n    out_blob           \u003d response_buf + 4 + next_smb2_rsp_hdr_off +\n                         SecurityBufferOffset(72)\n    out_len            \u003d response_sz - (72 + 4)\n    out_blob + out_len \u003d response_buf + response_sz + next_smb2_rsp_hdr_off\n\nThe window ksmbd_krb5_authenticate() thinks it may fill runs\nnext_smb2_rsp_hdr_off bytes past the end of the allocation. Any\nspnego_blob_len between the real remaining space and out_len passes the\ncheck and overflows the heap.\n\nout_blob follows the current compound slot, but out_len is still\ncomputed as if rsp were the first slot. Other handlers use the correct\npattern: smb2_resp_buf_len() computes response_sz -\n(get_rfc1002_len(response_buf) + 4) - hdr2_len, which accounts for space\nalready used.\n\n3. History of the two halves of the computation\n   \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nFrom git log -L over krb5_authenticate() and over the start of\nsmb2_sess_setup(), checked commit by commit:\n\n - e2f34481b24db (\"cifsd: add server-side procedures for SMB3\"),\n   v5.15-rc1:\n     rsp:     RESPONSE_BUF(work) \u003d response_buf, the first slot (the\n              old smb2_hdr still began with smb2_buf_length)\n     out_len: response_sz - offsetof(smb2_hdr, smb2_buf_length)[\u003d0]\n              - off\n     window:  ends at response_buf + response_sz + 4\n     status:  separate off-by-4: at most a 3-byte overflow, even\n              without compounding. Not this bug.\n\n - cb4517201b8ac (\"ksmbd: remove smb2_buf_length in smb2_hdr\"),\n   v5.16-rc1:\n     rsp:     smb2_get_msg(work-\u003eresponse_buf) \u003d response_buf + 4, the\n              first slot\n     out_len: response_sz - (off + 4) (today\u0027s expression)\n     window:  ends at response_buf + response_sz\n     status:  correct. Fixed the off-by-4.\n\n - dbad63001eac3 (\"ksmbd: validate compound response buffer\"),\n   v5.15-rc7: added the 448-byte headroom check in\n   is_chained_smb2_message(). Limits severity only.\n\n - 98422bdd4cb3c (\"ksmbd: fix out of bounds read in smb2_sess_setup\"),\n   v6.5-rc1:\n     rsp:     WORK_BUFFERS() -\u003e ksmbd_resp_buf_next() \u003d\n              response_buf + 4 + next_smb2_rsp_hdr_off\n     out_len: unchanged, response_sz - (off + 4)\n     window:  ends at response_buf + response_sz + next_smb2_rsp_hdr_off\n     status:  bug introduced.\n\n - 1e440d5b25b7e, e86e9134e1d1c, 7ac5b66acafcc, ecd9d6bf88ddd,\n4b706360ffb7e, 610346149d047, a47634cd729b4: touch\nsession/user/channel-key/expiry logic. None change rsp, out_blob or\nout_len (4b706360ffb7e only adds the auth_key argument).\n\n - 23a0be6a84a3 (the fix): out_len gains \"- next_smb2_rsp_hdr_off\", so\n   the window ends at response_buf + response_sz again. Fixed.\n\nThe start of smb2_sess_setup() confirms this. At e2f34481b24db,\ne5066499079de, 0d994cd482ee4, cb4517201b8ac and 98422bdd4cb3c^, rsp is\nalways the first slot. From 98422bdd4cb3c onward (and at e333e77638b3c,\nc8b5b7c5da7d0 and the fix\u0027s parent) it is WORK_BUFFERS(work, req, rsp).\n__wbuf() existed since e2f34481b24db for other handlers, but session\nsetup didn\u0027t use it until 98422bdd4cb3c.\n\n4. How 98422bdd4cb3c introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nZDI-CAN-21355 reported that a SESSION_SETUP sent as the second command\nof a compound was parsed from the first request slot, which caused an\nout-of-bounds read. Namjae fixed that by:\n\n 1. replacing req \u003d smb2_get_msg(work-\u003erequest_buf) /\n    rsp \u003d smb2_get_msg(work-\u003eresponse_buf) in smb2_sess_setup() with\n    WORK_BUFFERS(work, req, rsp);\n\n 2. removing the local first-slot req/rsp from ntlm_negotiate(),\n    ntlm_authenticate() and krb5_authenticate(), and passing the\n    compound-aware pointers in as arguments.\n\nThat fixed the read side. For krb5_authenticate(), though, out_blob is\nderived from rsp, so it moved forward by next_smb2_rsp_hdr_off. The\nout_len expression next to it was left unchanged. It was correct only\nwhile rsp was the first slot, and it still measures the space from slot\n0 to the end of the buffer. From that commit on, the base of the\npointer and the base of the length disagree, and the gap is exactly\nnext_smb2_rsp_hdr_off, which is the amount the fix subtracts.\n\nCounterfactual check:\n\n - Before 98422bdd4cb3c: a compound SESSION_SETUP still wrote its blob\n   into slot 0, and out_blob + out_len ended exactly at response_buf +\n   response_sz. Adding \"- next_smb2_rsp_hdr_off\" there would only have\n   shrunk a correct window.\n\n - At 98422bdd4cb3c: the fix\u0027s subtraction is exactly what makes the\n   window correct again.\n\n5. Why the earlier commits are not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - e2f34481b24db: its window was relative to the first slot, which\n   matched its first-slot rsp. Its only flaw was the off-by-4\n   (offsetof(smb2_hdr, smb2_buf_length) is 0, not 4), allowing at most\n3 bytes past the end. That is a different arithmetic error, unrelated\nto compounding, and cb4517201b8ac removed it in v5.16-rc1. Between v5.16\nand v6.5 this code had no OOB write.\n\n - e2b76ab8b5c93 (\"ksmbd: add support for read compound\"), v6.6-rc1:\nchanged iov/RFC1002 bookkeeping and dropped an inc_rfc1001_len in this\nfunction. It never touched out_blob/out_len/rsp.\n\n - dbad63001eac3: a mitigating guard, not a cause.\n\n6. Trigger conditions and impact\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Kerberos must be compiled in (CONFIG_SMB_SERVER_KERBEROS5), and the\n    connection must prefer KRB5/MSKRB5.\n\n 2. The SESSION_SETUP must not be the first command in a compound\n    request, so that next_smb2_rcv_hdr_off !\u003d 0 and\n    next_smb2_rsp_hdr_off \u003e 0. Earlier responses in the chain set how\n    far the window overshoots.\n\n 3. The attacker needs a valid Kerberos ticket.\n    ksmbd_krb5_authenticate() checks KSMBD_USER_FLAG_OK before the\n    length check, so the AP-REQ must authenticate successfully and\n    ksmbd.mountd must return an AP-REP.\n\n 4. Overflow size: at most\n    spnego_blob_len - (response_sz - 76 - next_smb2_rsp_hdr_off), and\n    always less than next_smb2_rsp_hdr_off. The write runs past a\n    kvzalloc(448 + max_trans_size) buffer.\n\n 5. The guard narrows the hole but doesn\u0027t close it. dbad63001eac3\n    requires get_rfc1002_len + 448 \u003c\u003d response_sz before each chained\n    command. That leaves roughly 365 bytes of real space after out_blob\n    in the worst case, so the AP-REP SPNEGO blob must be bigger than the\n    remaining space. The check meant to catch that is the one being\n    fooled.\n\n7. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe bug was introduced by 98422bdd4cb3c (\"ksmbd: fix out of bounds read\nin smb2_sess_setup\"), first in v6.5-rc1. The code before it, from\ncb4517201b8ac onward, computed out_len correctly for a first-slot rsp.\n98422bdd4cb3c rebased rsp onto the compound slot without rebasing\nout_len, and 23a0be6a84a3 repairs exactly that.\n\n98422bdd4cb3c was tagged Cc: stable and was backported to 5.15.y\n(676392184785a) and 6.1.y (ef572ffa8eb44), so the same mismatch likely\nreached those branches. The out_len arithmetic was only traced in\nmainline.\n\nAffected: v6.5 onward, plus the stable branches that took the backport.\nThe CVE announcement lists fixes in 6.6.160, 6.12.114, 6.18.58, 7.2.12\nand 7.3.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "dbb262f18c0ae6462161cbf3bc3241b5b2ae63e8",
      "tree": "c21e5a52adf5abcc9edcac7ffaa564965780b242",
      "parents": [
        "dd7c2dfe0cb1428093c4a33b4a5795d540ba58bf"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 18:15:59 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-99080: Add .vulnerable file\n\nThe root cause is 77da9389b9d5 (\"mm: Convert collapse_shmem to\nXArray\"), by Matthew Wilcox, first released in v4.20-rc1.\n\nThat commit made khugepaged\u0027s shmem collapse create xarray nodes for\nthe whole 2 MB range before it knew whether the collapse would succeed.\nNothing ever removed those nodes when the collapse gave up. The fix\n(ee3f01125010) doesn\u0027t touch the collapse code; it just frees whatever\nnodes are left over when the inode is torn down.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore the fix, clear_inode() only took and released the i_pages lock,\nunder a comment admitting that empty nodes can be left behind:\n\n    xa_lock_irq(\u0026inode-\u003ei_data.i_pages);\n    BUG_ON(inode-\u003ei_data.nrpages);\n    /* ... two known and long-standing ways in which nodes may get left behind\n     * (when deep radix-tree node allocation failed partway; or when THP\n     * collapse_file() failed) ... do not BUG_ON(!mapping_empty) ... */\n    xa_unlock_irq(\u0026inode-\u003ei_data.i_pages);\n\nThe fix replaces this with xa_destroy(\u0026inode-\u003ei_data.i_pages). That\nstill cycles the lock, which reclaim needs, and it also frees every node\nleft in the tree.\n\nThe fix has no Fixes: tag. Its message says the leak has existed \"for\nmany years\", points at Hugh\u0027s 786b31121a2c, and says \"people and syzbot\nhave found ways to force it to happen with madvise\". The madvise path is\nMADV_COLLAPSE -\u003e collapse_file().\n\n2. Why an empty node is never freed\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Nodes are only freed when a store empties them. update_node() calls\n   xas_delete_node() only when a store lowers the node\u0027s count\n(\"if (count \u003c 0)\"). A node that was created but never held an entry has\ncount 0, and nothing ever triggers its deletion. Hugh tried storing NULL\ninto those slots in 2021; nothing changes, because the count is already\n0.\n\n - Truncate can\u0027t reach them. truncate_inode_pages*() and\n   shmem_truncate_range() walk entries. A node with no entries is never\n   visited. Truncate frees the leaves that did hold pages, which lowers\n   the parent\u0027s count, but the parent never reaches 0 because it still\n   points at the empty leaves.\n\n - clear_inode() never freed nodes. It assumed the mapping was already\n   empty.\n\n - i_pages is only set up in the slab constructor. xa_init_flags() runs\n   in inode_init_once() -\u003e __address_space_init_once(), not per\n   allocation. So the stale xa_head either passes the orphaned nodes to\n   the next inode that reuses that slab object (Hugh saw exactly this:\n\"anyone who inherits that struct inode thereafter inherits these\nnodes\"), or the nodes leak for good once the slab page is freed.\n   kmemleak reports them as 576-byte xa_node objects.\n\n - They are charged to a memcg. i_pages has XA_FLAGS_ACCOUNT, so the\n   leaked nodes stay charged to the cgroup that triggered the collapse.\n\n3. The radix-tree version did not leak\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nJust before 77da9389b9d5 (from f3f0e1d2150b2, v4.8), collapse_shmem()\nfilled holes one by one as it walked the range:\n\n    radix_tree_for_each_slot(slot, \u0026mapping-\u003ei_pages, \u0026iter, start) {\n            ...\n            for (; index \u003c min(iter.index, end); index++)\n                    radix_tree_insert(\u0026mapping-\u003ei_pages, index,\n                                      new_page + (index % HPAGE_PMD_NR));\n\nThe rollback deleted the holes it had filled:\n\n    if (!nr_none) break;\n    nr_none--;\n    /* Put holes back where they were */\n    radix_tree_delete(\u0026mapping-\u003ei_pages, iter.index);\n\nSo a node was only created together with an entry stored in it. On\nfailure, radix_tree_delete() emptied it and delete_node() freed it.\nHoles after the failure point were never touched, so no node existed\nfor them.\n\n4. What 77da9389b9d5 changed\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n    +\tXA_STATE_ORDER(xas, \u0026mapping-\u003ei_pages, start, HPAGE_PMD_ORDER);\n    ...\n    +\t/* This will be less messy when we use multi-index entries */\n    +\tdo {\n    +\t\txas_lock_irq(\u0026xas);\n    +\t\txas_create_range(\u0026xas);\n    +\t\tif (!xas_error(\u0026xas))\n    +\t\t\tbreak;\n    +\t\txas_unlock_irq(\u0026xas);\n    +\t\tif (!xas_nomem(\u0026xas, GFP_KERNEL))\n    +\t\t\tgoto out;\n    +\t} while (1);\n\nxas_create_range() (added for this caller in 2264f5132fe4, earlier in\nthe same v4.20 series) does what its documentation says: it creates\nevery node needed to store into all 512 slots. With 64-slot nodes, that\nmeans up to 8 leaf nodes under one parent node, plus any upper levels\nneeded to grow the tree. Leaves covering 64-page stretches with no pages\nare created empty.\n\nThe rollback was converted to:\n\n    xas_for_each(\u0026xas, page, end - 1) {\n            ...\n            if (!nr_none) break;\n            nr_none--;\n            xas_store(\u0026xas, NULL);  /* Put holes back where they were */\n\nxas_for_each() only visits slots that hold an entry. It cannot see the\nnodes that xas_create_range() created for parts of the range the main\nloop never reached. Every failure exit therefore leaks them, already at\nthis commit:\n\n - xas_nomem() fails -\u003e goto out, leaving the partly built nodes.\n\n - trylock_page() fails (SCAN_PAGE_LOCK), shmem_charge() fails, or\n   shmem_getpage() fails (SCAN_FAIL) part way through. Leaves covering\n   only holes past the failure point stay at count 0.\n\nThe helper behaves as documented. The bug is the caller creating nodes\nahead of time with no way to remove the unused ones on failure. That\nmakes 77da9389b9d5 the root cause rather than 2264f5132fe4, and\ncollapse_file() is still the only caller of xas_create_range() in\norigin/master.\n\n5. Later commits that made it easier to hit (not root causes)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 701270fa193a (\"mm/khugepaged: collapse_shmem() stop if punched or\n   truncated\"), v4.20-rc5: added the\n\"index \u003d\u003d start \u0026\u0026 !xas_next_entry()\" -\u003e SCAN_TRUNCATED exit. An empty\nrange now leaks all the nodes it just created.\n\n - 99cb0dbd47a1 (\"mm,thp: add read-only THP support for (non-shmem)\nFS\"), v5.4: renamed the function to collapse_file() and applied it to\npage-cache files. This is how Hugh hit it on ext4 (/usr/bin/ld.bfd).\n\n - 8bc3c481b3d0 -\u003e 786b31121a2c, v5.13: briefly detected the leak with\nBUG_ON(!mapping_empty()), then removed the check and documented both\nleak paths.\n\n - 7d8faaf155454 (\"mm/madvise: introduce MADV_COLLAPSE sync hugepage\n   collapse\"), v6.1: for MADV_COLLAPSE, collapse_max_ptes_none() returns\nHPAGE_PMD_NR, so even a completely empty range reaches collapse_file().\nThe leak becomes deterministic from userspace.\n\n - cae106dd67b9 (\"mm/khugepaged: refactor collapse_file control flow\"),\n   v6.4: moved hole filling (XA_RETRY_ENTRY) after the scan; the main\n   loop sets nr_none \u003d 0 before rollback. Pre-created nodes are still\n   never removed.\n\n6. The trigger in today\u0027s code\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThis is the reproducer from Jinjiang Tu and Rik van Riel, traced\nthrough origin/master:\n\n 1. A 4 MB shmem file has its first 2 MB written. The tree is one\n    0-4095 node with 8 full leaves.\n\n 2. MADV_COLLAPSE on the second 2 MB -\u003e collapse_scan_file() -\u003e\n    collapse_file().\n\n 3. xas_create_range() creates 8 empty leaves for pages 512-1023. The\n    parent\u0027s count goes from 8 to 16.\n\n 4. The page at start is missing and so is everything up to end - 1, so\n    the function returns SCAN_TRUNCATED. The rollback only touches\n    mapping-\u003enrpages when nr_none is non-zero, and here it is 0, so the\n    xarray is left as it is.\n\n 5. Unlinking the file runs shmem_truncate_range(). It frees the 8\n    populated leaves, and the parent\u0027s count drops back to 8. The parent\n    survives because it still points at the 8 empty leaves.\n\n 6. clear_inode() just cycles the lock, so 9 nodes (about 5 KB) are\n    orphaned.\n\nkmemleak shows xas_alloc \u003c- xas_create \u003c- xas_create_range \u003c-\ncollapse_file \u003c- madvise_collapse.\n\n7. The other path the fix also covers\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n\"Radix tree expansion can fail partway\" is a separate and much rarer\npath. xas_create() links an upper node into the tree, then fails to\nallocate the next level and leaves the upper node empty. Willy calls\nthis \"a problem inherited from the radix tree\". The same pattern is\nalready in radix_tree_insert() in 1da177e4c3f4, the first commit in the\ngit history, so it can\u0027t be pinned to any commit. It isn\u0027t the\nmadvise/syzbot path this CVE is about.\n\nEvidence that 77da9389b9d5 is the right answer\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Two independent upstream fixes for this exact leak name it in their\n   Fixes: tag: Jinjiang Tu\u0027s RFC\n\u003c20260121062243.1893129-1-tujinjiang@huawei.com\u003e, which the fix commit\nlinks to, and Rik van Riel\u0027s syzkaller patch\n\u003c20260616145413.1491961-1-riel@surriel.com\u003e. Willy answered both with\nthis clear_inode() patch.\n\n - In the 2021 thread, Willy responded to Hugh\u0027s xarray dumps (a parent\n   node pointing to 1-3 empty leaves) with \"Urp, yes, that can easily\n   happen\" and quoted the xas_create_range() loop from 77da9389b9d5.\n\n - Shardul Bankar\u0027s syzbot patches blaming cae106dd67b9 are about a\n   different leak: an unused spare node left in xas-\u003exa_alloc.\n\nAffected: v4.20 onward. The CVE announcement lists fixes in 6.6.160,\n6.12.113, 6.18.57, 7.2.11 and 7.3.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "dd7c2dfe0cb1428093c4a33b4a5795d540ba58bf",
      "tree": "23820b20b1374d350058ff6218b4baab843fd02d",
      "parents": [
        "b688214651369d9b511ff8a6e1d742319ab2f82c"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 18:06:08 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-99036: Add .vulnerable file\n\nThe root cause is d58168763f74 (\"dm table: rework reference counting\"),\nMikulas Patocka, January 2009, first released in v2.6.29-rc1.\n\nThat commit made do_resume() stop holding its own reference to new_map.\nThe dm_table_get_mode(new_map) call after dm_swap_table() stayed where\nit was, so from then on it could read a table that another thread had\nalready freed.\n\nThe fix, c9417c1c5269 (\"dm: fix reading free memory in do_resume\"), has\nno Fixes: tag, so this comes entirely from tracing the history in\norigin/master.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe bug is in drivers/md/dm-ioctl.c, do_resume(), on the path where a\nnew table gets loaded. Before the fix it looked like this:\n\n    new_map \u003d hc-\u003enew_map;          // taken out of the hash cell under _hash_lock\n    hc-\u003enew_map \u003d NULL;\n    up_write(\u0026_hash_lock);          // from here on, no locks held\n    ...\n    old_map \u003d dm_swap_table(md, new_map);   // new_map is now md-\u003emap, the live table\n    ...\n    if (dm_table_get_mode(new_map) \u0026 BLK_OPEN_WRITE)   // reads new_map-\u003emode\n            set_disk_ro(dm_disk(md), 0);\n\nOnce dm_swap_table() returns, new_map is the device\u0027s live table. The\nonly thing that owns it is md-\u003emap, which is not reference-counted.\ndo_resume() holds a reference to md, but nothing on the table itself.\n\nThe fix reads the mode into new_map_mode before the swap, while the\ntable still belongs only to this thread.\n\n2. How the race happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nmd-\u003esuspend_lock is taken separately inside dm_suspend(),\ndm_swap_table() and dm_resume(). _hash_lock is dropped before any of\nthem. Nothing serializes two whole DM_DEV_SUSPEND ioctls against each\nother: the ioctl handler is .unlocked_ioctl and has been since well\nbefore 2009.\n\n - Thread A: DM_TABLE_LOAD T1, then DM_DEV_SUSPEND (resume).\n\n - Thread A: takes T1 out of hc-\u003enew_map, drops _hash_lock.\n\n - Thread A: dm_suspend(md).\n\n - Thread A: dm_swap_table(md, T1). T1 is now live; the device is still\n   suspended.\n\n - Thread B: DM_TABLE_LOAD T2 into hc-\u003enew_map.\n\n - Thread B: DM_DEV_SUSPEND (resume). Takes T2; the device is already\n   suspended, so it skips dm_suspend.\n\n - Thread B: dm_swap_table(md, T2). The suspended check passes and T1\n   comes back as old_map.\n\n - Thread B: dm_resume(md), then dm_table_destroy(T1), which ends in\n   kfree(T1).\n\n - Thread A: dm_table_get_mode(T1): use-after-free.\n\nB\u0027s swap succeeds only because A swapped while suspended and hasn\u0027t\nresumed yet. That is exactly A\u0027s window between the swap and\ndm_table_get_mode(). In current kernels dm_table_destroy() no longer\nwaits for anything (no holders count) and frees right away.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -S\u0027dm_table_get_mode(new_map)\u0027 points only at 1da177e4c3f4\n(Linux-2.6.12-rc2). The call itself is as old as git. The question is\nwhen it became unsafe.\n\n3a. In 2.6.12 (1da177e4c3f4) the code was safe\n----------------------------------------------\n\nTables were truly reference-counted then:\n\n - dm_table_create() set holders \u003d 1. That reference belonged to\n   hc-\u003enew_map.\n\n - do_resume() did \"new_map \u003d hc-\u003enew_map; hc-\u003enew_map \u003d NULL;\", which\n   handed that reference to the local variable.\n\n - dm_swap_table() called __bind(), which took a second reference with\n   dm_table_get(t).\n\n - do_resume() only dropped its reference after reading the mode:\n\n    r \u003d dm_swap_table(md, new_map);\n    ...\n    if (dm_table_get_mode(new_map) \u0026 FMODE_WRITE)\n            set_disk_ro(dm_disk(md), 0);\n    else\n            set_disk_ro(dm_disk(md), 1);\n\n    dm_table_put(new_map);      // \u003c-- do_resume\u0027s own reference, dropped only here\n\nIf a concurrent swap removed T1, its __unbind() called dm_table_put()\nand holders went from 2 to 1. Thread A\u0027s reference kept T1 alive through\ndm_table_get_mode().\n\nThe later commits before 2009 (aa8d7c2fbe61, 6da487dcc0c6,\n7ec75f254742, a3d77d35be6f, 81fdb096dbce) only changed suspend flags\nand md refcounting. At d58168763f74^ the trailing dm_table_put(new_map)\nis still there.\n\n3b. d58168763f74 removed the protection\n---------------------------------------\n\nThis commit changed the ownership model, as its own message says: \"The\ntable has always exactly one reference from either mapped_device-\u003emap or\nhash_cell-\u003enew_map. After this patch, this reference is not counted in\ntable-\u003eholders.\" holders now only counts short-lived references, and\ndm_table_destroy() frees the table outright once holders reaches 0.\n\nThe relevant hunks:\n\n    @@ do_resume\n     \t\tr \u003d dm_swap_table(md, new_map);\n     \t\tif (r) {\n    +\t\t\tdm_table_destroy(new_map);\n     \t\t\tdm_put(md);\n    -\t\t\tdm_table_put(new_map);\n     \t\t\treturn r;\n     \t\t}\n\n     \t\tif (dm_table_get_mode(new_map) \u0026 FMODE_WRITE)\n     \t\t\tset_disk_ro(dm_disk(md), 0);\n     \t\telse\n     \t\t\tset_disk_ro(dm_disk(md), 1);\n    -\n    -\t\tdm_table_put(new_map);\n     \t}\n\n    @@ __bind\n    -\tdm_table_get(t);\n    @@ __unbind\n    -\tdm_table_put(map);\n    +\tdm_table_destroy(map);\n\nAfter this:\n\n - do_resume() holds no reference on new_map, neither a counted one nor\n   through hc.\n\n - Once the swap is done, md-\u003emap is the only owner, and it is\n   uncounted.\n\n - A second do_resume() calling dm_swap_table() ran __unbind(), which\n   called dm_table_destroy(T1). That waited only for holders, which\n   thread A never incremented, and then freed T1.\n\n - Thread A then read t-\u003emode from the freed memory.\n\nBefore this commit the window was protected and after it the window is\nopen. dm_table_get_mode(new_map) wasn\u0027t moved before the swap, which\nwould have been the safe ordering, so this commit is the root cause.\n\nA side note on the same commit: it also made __bind() call\ndm_table_destroy(t) for a zero-size table and return 0. do_resume()\nwould then call dm_table_get_mode() on the table it had just freed, with\nno race at all. In practice this couldn\u0027t be reached, because\npopulate_table() rejects empty tables. 042d2a9bcd80 removed that branch.\n\n3c. Later commits kept the bug without changing it\n--------------------------------------------------\n\n - 042d2a9bcd80 (\"dm: keep old table until after resume succeeded\"),\n   v2.6.33-rc1: dm_swap_table() now returns old_map, and the caller\n   destroys it after dm_resume(). Thread B now frees T1 at the end of\n   its own do_resume() instead of inside __unbind(). Thread A\u0027s\n   unprotected read after the swap is unchanged, so the race is the\n   same.\n\n - 83d5e5b0af90 (\"dm: optimize use SRCU and RCU\"): dm_sync_table() and\n   SRCU protect readers that go through dm_get_live_table(). do_resume()\n   uses its local pointer directly and never enters an SRCU read\n   section, so it still isn\u0027t protected.\n\n - 4f186f8bbfa9, 0f3649a9e305, 8eb6fab402e2, 7533afa1d27b,\n05bdb9965305, 5534cac9b56d, and others: renames, uevent handling,\ndm-ima additions, and the fmode_t to blk_mode_t change. None of them\ntook a reference on new_map again.\n\n - dm_ima_need_measure(md, new_map, ...), which also runs after the\n   swap: it only compares the pointer to the live table under\n_hash_lock and SRCU and never dereferences it. That matches the fix\u0027s\nown note that this call is safe.\n\n4. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause commit: d58168763f74 \"dm table: rework reference\n   counting\" (Mikulas Patocka, v2.6.29-rc1).\n\n - How it caused the bug: it changed md-\u003emap from a counted reference\n   to the table\u0027s only, uncounted owner. It removed do_resume()\u0027s own\n   dm_table_put(new_map) reference and __bind()\u0027s dm_table_get(). It\n   left the dm_table_get_mode(new_map) call after dm_swap_table() in\n   place, where a concurrent table swap and destroy on the same\n   suspended device can free the table first.\n\n - Not the root cause: 1da177e4c3f4 is where the\n   dm_table_get_mode(new_map) line comes from, but it was safe there\n   because do_resume() held a reference until after the read.\n\n - Affected range: v2.6.29 through the fix in c9417c1c5269. Triggering\n   it needs CAP_SYS_ADMIN (root) and concurrent DM_TABLE_LOAD and resume\n   ioctls on the same device.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "b688214651369d9b511ff8a6e1d742319ab2f82c",
      "tree": "a8d4b432a1c4354d26c1e51125f8a6f0fa5bebe6",
      "parents": [
        "646ee2666d64c549ba2d0313eb7846d1f31d239b"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 18:00:51 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-98945: Add .vulnerable file\n\nThe root cause is 9cd61c8179781 (\"HID: hid-steam: Add rumble on Deck\"),\nmerged for v6.3-rc1.\n\nThe fix, ddce9641ce924 (\"HID: steam: Rearrange teardown sequence\"), is\nin v7.3-rc1 and has no Fixes: tag. The tag it should carry is:\n\n    Fixes: 9cd61c817978 (\"HID: hid-steam: Add rumble on Deck\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore the fix, steam_remove() did this:\n\n    hid_destroy_device(steam-\u003eclient_hdev);\n    cancel_delayed_work_sync(\u0026steam-\u003emode_switch);\n    cancel_work_sync(\u0026steam-\u003ework_connect);\n    cancel_work_sync(\u0026steam-\u003erumble_work);              // cancelled here...\n    cancel_delayed_work_sync(\u0026steam-\u003ecoalesce_rumble_work);\n    steam-\u003eclient_hdev \u003d NULL; steam-\u003eclient_opened \u003d 0;\n    hid_hw_close(hdev);\n    hid_hw_stop(hdev);\n    steam_unregister(steam);                            // ...but the input device is only removed here\n\nWhen steam_remove() returns, the HID core releases the driver\u0027s devres\ngroup and frees the devm-allocated struct steam_device. The work items\nlive inside that struct.\n\nThe fix makes three changes:\n\n - It moves the rumble, coalesce and mode-switch cancels into\n   steam_unregister(), after steam_input_unregister().\n\n - It moves hid_hw_close() to the start of steam_remove().\n\n - It cancels work_connect after client_opened is cleared under the\n   spinlock, and it moves hid_hw_stop() to the end.\n\nThis hunk was already in v1 (2026-07-02), so it came from the author,\nnot from a review comment.\n\n2. Which \"callbacks scheduled during cleanup\" can actually happen\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe driver has four work items. For each one, the question is whether\nanything can re-queue it after its cancel.\n\nwork_connect and mode_switch (raw HID events): not reachable\n-------------------------------------------------------------\n\nSashiko said in its reviews of v1, v2, v3 and v5 that an incoming\nreport could re-queue these after the cancel, and that steam_raw_event()\ncould use a freed client_hdev. That is a false positive:\n\n - hid_device_remove() takes hdev-\u003edriver_input_lock and sets\n   io_started \u003d false before calling -\u003eremove (drivers/hid/hid-core.c).\n\n - __hid_input_report() uses down_trylock() on that lock and returns\n   -EBUSY while it is held.\n\n - hid-steam never calls hid_device_io_start().\n\n - So steam_raw_event() cannot run at any point during steam_remove().\n   This has been true since c849a6143bec5 (v3.10).\n\n - The only other source of work_connect is the client hidraw\n   open/close. That source was already shut down before the cancel by\n   e53fc232a65f7 (v6.14-rc6).\n\nrumble_work and coalesce_rumble_work (force feedback): reachable\n-----------------------------------------------------------------\n\nThese are queued only from steam_play_effect(), the memless\nforce-feedback callback of steam-\u003einput. That input device stays\nregistered until steam_unregister(), which runs last, after the cancels.\nThree paths re-queue the work:\n\n 1. Unregistering the input device re-queues it every time. This\n    happens whenever a client has the evdev node open with an effect\n    playing:\n\n      steam_unregister -\u003e steam_input_unregister -\u003e input_unregister_device\n        -\u003e evdev_disconnect -\u003e evdev_cleanup -\u003e input_flush_device(handle, NULL)\n        -\u003e input_ff_flush -\u003e erase_effect -\u003e ff-\u003eplayback(dev, id, 0)\n        -\u003e ml_ff_playback -\u003e ml_play_effects -\u003e steam_play_effect\n        -\u003e schedule_work(\u0026steam-\u003erumble_work)    [+ schedule_delayed_work(coalesce, HZ/20)]\n\n    With file \u003d\u003d NULL, check_effect_access() lets every effect be\n    erased. A playing effect becomes \"aborting\", and\n    ml_get_combo_effect() then passes a zero-magnitude rumble to\n    play_effect. All of this runs after\n    cancel_work_sync(\u0026steam-\u003erumble_work).\n\n 2. Userspace can trigger it directly in the gap between the cancel and\n    steam_unregister() (which includes the slow\n    hid_hw_close()/hid_hw_stop()). An EV_FF write or EVIOCSFF call lands\n    in steam_play_effect().\n\n 3. The memless effect timer can fire in the same gap and reach\n    steam_play_effect() the same way.\n\nAfter steam_remove() returns and steam is freed,\nsteam_haptic_rumble_cb() or steam_coalesce_rumble_cb() runs on freed\nmemory. It does container_of, reads rumble_left/rumble_right, locks\nreport_mutex, and sends a report through steam-\u003ehdev. The workqueue is\nalso still handling a work_struct inside freed memory. The coalesce work\nis delayed by 50 ms and re-queues itself, so in v7.3-rc1 it almost\ncertainly runs after the free.\n\nThe fix cancels both works after steam_input_unregister() has run its\nfinal flush. Once the evdev is dead, nothing else can call\nsteam_play_effect().\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 9cd61c8179781 (\"HID: hid-steam: Add rumble on Deck\"), v6.3-rc1.\nRoot cause. Adds rumble_work, queued from\ninput_ff_create_memless(input, NULL, steam_play_effect). It cancels the\nwork only on the probe error path; steam_remove() never cancels it.\nsteam_unregister() was already the last call in remove, so the\nflush-time re-queue and any pending rumble both run on freed memory.\n\n - cc4f952427aaa (\"HID: hid-steam: Make sure rumble work is canceled on\n   removal\"), v6.14-rc1. Incomplete fix. Adds\n   cancel_work_sync(rumble_work) but places it before\n   steam_unregister(), while the input device that queues the work is\n   still alive. This is exactly the window ddce9641 closes.\n\n - 1d4549fd1115a (\"HID: steam: Coalesce rumble packets\"), v7.3-rc1.\nCopies the same misplaced cancel for the new delayed, self-requeueing\ncoalesce_rumble_work, which widens the window.\n\n - ddce9641ce924 (\"HID: steam: Rearrange teardown sequence\"), v7.3-rc1.\n   Moves the cancels after input unregistration.\n\nWhy the other candidates are not the root:\n\n - c164d6abf3841 (original driver, v4.18) and cd438e57dd05b\n(mode_switch, v6.8): they also cancel before closing the hardware, but\ntheir only re-queue source is raw events, which the HID core blocks\nduring remove (section 2). The original driver queued no work from\ninput-device callbacks.\n\n - 9ba9498bff1a3: its Deck heartbeat work was armed from\n   steam_input_open() and cancelled again by steam_input_close() on\n   unregister, so it was safe. It was removed in v6.8 anyway.\n\n - 79504249d7e27 / e53fc232a65f7: these concern the client hidraw -\u003e\n   work_connect path, which is a separate bug that was already fixed.\n\nThe underlying defect: 9cd61c8179781 tied a work item to an event\nsource (the force-feedback input device) that outlives every cancel in\nthe teardown sequence. The 7.2.y/6.x stable backports of the fix leave\nout the coalesce hunk, which confirms the bug exists without\n1d4549fd1115a.\n\n4. Exposure\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Hardware/config: only the Steam Deck (STEAM_QUIRK_DECK) with\n   CONFIG_STEAM_FF\u003dy.\n\n - Trigger: a process holds the evdev node open and plays rumble while\n   the driver is removed (unbind, rmmod, USB disconnect/reset, or a\n   uhid-emulated device being destroyed).\n\n - Vulnerable range: v6.3 through v7.3-rc1 before the fix.\n\n - Fixed in stable: 6.6.160, 6.12.113, 6.18.57, 7.2.11.\n\n - Not affected: 6.1.y, which has no rumble_work.\n\n - Still unfixed: 7.0.y (v7.0.14) and 7.1.y (v7.1.13) contain\n   rumble_work and no backport.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "646ee2666d64c549ba2d0313eb7846d1f31d239b",
      "tree": "5086877cb4a8f859a8074571458134862fe84a7e",
      "parents": [
        "86111d6580b76764f111ddb803c5cab1d63521de"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 17:49:42 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-98944: Add .vulnerable file\n\nThe root cause is 385a4886778f6 (\"HID: steam: remove input device when\na hid client is running.\"), by Rodrigo Rivas Costa, merged for\nv4.20-rc4.\n\nThat commit split the driver\u0027s registration code and moved the battery\nregistration in front of the input registration, which can fail. It\nadded no code to undo the battery when that later step fails. It also\nstarted using serial_no[0] as a hidden \"this device is registered\" flag.\nThe fix, 6f9b740c31ba7 (\"HID: steam: Refactor registration\"), removes\nboth mistakes. The tag it should carry is:\n\n    Fixes: 385a4886778f (\"HID: steam: remove input device when a hid client is running.\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIn steam_register() before the fix:\n\n    if (!steam-\u003eserial_no[0]) {                 /* serial number used as \"registered\" flag */\n            steam_get_serial(...) / steam_get_attributes(...)\n            if (steam-\u003equirks \u0026 STEAM_QUIRK_WIRELESS)\n                    steam_battery_register(steam);   /* power_supply created here */\n            do_add \u003d true;\n    }\n    ...\n            ret \u003d steam_input_register(steam);   if (ret) goto steam_register_input_fail;\n            ret \u003d steam_sensors_register(steam); if (ret) goto steam_register_sensors_fail;\n    ...\n    steam_register_sensors_fail:\n            steam_input_unregister(steam);\n    steam_register_input_fail:\n            return ret;                          /* battery is NOT unregistered */\n\nsteam_unregister() started with \"if (!steam-\u003eserial_no[0]) return;\".\n\nThe fix:\n\n - adds steam-\u003eregistered, set only when registration fully succeeds\n   and checked by both functions instead of serial_no[0];\n\n - substitutes \"XXXXXXXXXX\" when the device returns an empty serial;\n\n - adds steam_battery_unregister() plus the work cancellations to the\n   failure path.\n\nWhy a leftover battery is a use-after-free. steam_battery_register()\ncreates the battery with:\n\n - drv_data \u003d steam, the driver\u0027s per-device structure, which is freed\n   automatically (devm_kzalloc) when the device goes away;\n\n - desc \u003d \u0026steam-\u003ebattery_desc, which lives inside that same structure;\n\n - a name string (devm_kasprintf) that is also freed automatically with\n   the device.\n\nIf the battery outlives the structure, any sysfs read or kernel walk of\nthe power-supply class reaches freed memory. A sysfs read goes through\nsteam_battery_get_property() -\u003e spin_lock_irqsave(\u0026steam-\u003elock).\n\n2. How steam_register() evolved\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nc164d6abf3841 (v4.18), the original driver:\n\n - steam_register() checked steam-\u003einput for \"already connected\" and\n   read the serial every time.\n\n - serial_no was never used as a state flag.\n\nf827197907517 (\"HID: steam: add battery device.\"):\n\n - The battery was registered as the last step, only after\n   input_register_device() succeeded. A failed registration could\n   therefore never leave a battery behind.\n\n - steam_unregister() removed the battery and input purely based on\n   whether their pointers were set.\n\n385a4886778f6 (v4.20-rc4), the \"previous refactor\" the fix refers to:\n\n - The old function became steam_input_register(), and a new wrapper\n   steam_register() was written.\n\n - The serial read, steam_battery_register() and list_add(\u0026steam-\u003elist,\n   \u0026steam_devices) moved into a block that runs once per device, gated\n   by \"if (!steam-\u003eserial_no[0])\". That block runs before\n   steam_input_register().\n\n - If steam_input_register() fails, the function just \"return ret;\".\n   Nothing undoes the battery or the list entry.\n\n - steam_unregister() began using serial_no[0] to decide about the list\n   entry and the \"disconnected\" message, then cleared\n\"serial_no[0] \u003d 0\". This is the \"assumption that we have a serial\nnumber to show we\u0027re registered\" that the fix removes.\n\nThe same refactor left the list entry un-undone too. On a wired device\nwhose probe failed, the error path skipped steam_unregister() and left a\nfreed steam on the global steam_devices list.\n\nLater commits carried the defect forward without fixing it:\n\n - 6b538cc21334b, 2d3f53a80e4ee, eeeec27d68204, 691ead124a0c3,\n   555b818adb97e: locking and string-copy changes only.\n\n - 3347e1654f24d (\"Add Deck IMU support\", v6.11): added the\n   steam_register_sensors_fail / steam_register_input_fail labels. A\n   sensors failure undoes the input device, but the battery is still\n   never undone.\n\n - 2eb7cf02b5215: added steam_get_attributes() to the once-per-device\n   block.\n\n - cd33a91d37eb4 (\"Fully unregister controller when hidraw is opened\",\n   July 2026, also backported to 6.6/6.12/6.18/7.2):\n\n     - moved list_add after full success (the do_add variable), which\n       fixed the list half of the problem;\n\n     - left the battery half unfixed;\n\n     - made all of steam_unregister(), including the battery removal,\n       bail out when !serial_no[0].\n\n3. When the leftover battery outlives teardown\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Before cd33a91d37eb4 the defect was latent. A stray battery from a\n    failed registration was still removed later, because\n    steam_remove() -\u003e steam_unregister() called\n    steam_battery_unregister() unconditionally.\n\n 2. In the pre-fix tree the leak is reachable with an empty serial. A\n    real or spoofed 28de:1142 wireless dongle can answer the serial\n    request with length 1, or with a leading NUL byte.\n    steam_get_serial() then succeeds but leaves serial_no empty.\n    Registration creates \"steam-controller--battery\" plus the input and\n    sensor devices. On unplug, steam_unregister() returns early because\n    the serial is empty, so all of them outlive the freed steam\n    structure.\n\n 3. Any device that registers a battery from probe hits it directly. On\n    a failed steam_register(), probe jumps to err_hw_close and skips\n    steam_unregister(). The structure is freed with the battery still\n    live. This is the scenario Sashiko\u0027s review of the v1 2026-controller\n    patch reported, and it is what prompted the fix.\n\n4. Mailing-list evidence\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - The fix began as \"[PATCH v2 1/6] HID: steam: Unregister battery on\n   partial registration error\". It was a one-line\n   steam_battery_unregister() in the failure path, with the same\n   \"previous refactor ... accidentally left out\" wording.\n\n - The v2 cover letter says the missing battery unregistration \"made it\n   through the last series\". That means it survived the July cleanup\n   series; none of that series\u0027 versions ever contained it.\n\n - v4 broadened the patch into \"Refactor registration\" with the\n   registered flag. v5 added the work cancellations after Sashiko\n   pointed out that the new flag skipped them on a failed registration.\n\nThe failure path of steam_register() has never contained a battery\nunregister in any version since 385a4886778f6 created the function.\nThat commit is where battery registration was first placed before a\nstep that can fail, with no undo, and where serial_no became the\nregistration flag.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "86111d6580b76764f111ddb803c5cab1d63521de",
      "tree": "ef65a819887a2f3dc8b108b6715735f65b29ddf4",
      "parents": [
        "fb47e6a8ac80d9c83327b6a6fcb9f5d8f86c24a9"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 17:37:40 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-98943: Add .vulnerable file\n\nThe root cause is 0a80b4e8ec6a6 (\"HID: steam: Initial 2026 Steam\nController support\"), by Vicki Pfau. That commit created both real bugs\nthat de1d341b9b3ec (\"HID: steam: Clean up locking\") fixes. The fix has\nno Fixes: tag, and nothing older introduced them. The tag it should have\ncarried is:\n\n    Fixes: 0a80b4e8ec6a (\"HID: steam: Initial 2026 Steam Controller support\")\n\nBoth commits are from the same series, v5 of \"HID: steam: Add 2026\nSteam Controller support\" on lore. 0a80b4e8ec6a6 is patch 2/6 and the\nfix is patch 5/6, and both entered mainline in v7.3-rc1 through the same\nmerge. So no tagged release has the bug without the fix; only bisection\npoints and partial backports do. Note that the fix is not the\n2023 commit with the same title, 691ead124a0c3 (\"HID: hid-steam: Clean\nup locking\").\n\nLine numbers below are in drivers/hid/hid-steam.c at the fix\u0027s parent,\nde1d341b9b3ec~1.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Adds registration_mutex around steam_register() and\n    steam_unregister(). This is the \"RCU accesses not being guarded\n    behind a lock\" in the commit message: the code that publishes and\n    clears steam-\u003einput, steam-\u003esensors and steam-\u003ebattery had no lock.\n\n 2. Removes the report_mutex guard from steam_exchange_report_id() (and\n    from the haptic helpers and steam_set_lizard_mode()). Callers now\n    hold the mutex, including a new hold in steam_register() around the\n    serial, attributes and lizard-mode calls.\n\n 3. Smaller data-race cleanups:\n\n     - steam_raw_event() now runs under steam-\u003elock, so the nested\n       spinlocks in the connect and battery event handlers are dropped.\n\n     - gamepad_mode is toggled under the lock in steam_mode_switch_cb().\n\n     - client_opened is read under the lock in\n       steam_param_set_lizard_mode().\n\nItems 1 and 2 are the real bugs. Both come from 0a80b4e8ec6a6.\n\n2. Bug 1: report_mutex taken twice on the same path, which hangs probe\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAt the parent:\n\n    static int steam_get_conn_status(struct steam_device *steam)          // :720\n    {\n            ...\n            guard(mutex)(\u0026steam-\u003ereport_mutex);                            // :732  lock #1\n            ret \u003d steam_exchange_report_id(steam, cmd, ..., report_id);    // :733\n    ...\n    static int steam_exchange_report_id(...)                              // :617\n    {\n            ...\n            guard(mutex)(\u0026steam-\u003ereport_mutex);                            // :623  lock #2, same mutex\n\nKernel mutexes are not recursive, so the second mutex_lock blocks its\nown task forever. steam_probe() calls steam_get_conn_status() for every\nSTEAM_QUIRK_WIRELESS device (:1724): the 2015 wireless dongle (once per\nslot interface), the 2026 Puck and the Steam Machine internal receiver.\n\nThe result:\n\n - Probe hangs while holding the device lock.\n\n - The work_connect worker then blocks on the same mutex in\n   steam_get_serial().\n\n - Unplugging the device cannot complete, and you get hung-task\n   warnings.\n\nHow the deadlock was assembled:\n\n - de435b770cd94 (\"HID: steam: Improve logging and other cleanup\"):\n   converted the mutex in steam_request_conn_status() to guard(mutex).\n   That function only sent one byte with steam_send_report_byte().\n   Deadlock on its own: no.\n\n - fcfa7db89f7a0 (\"HID: steam: Retry send/recv reports if stale\"):\nadded steam_exchange_report() with its own guard(mutex), and removed\nthe guards from its only callers, steam_get_serial() and\nsteam_get_attributes(). Deadlock on its own: no.\n\n - 0a80b4e8ec6a6 (\"HID: steam: Initial 2026 Steam Controller support\"):\nrewrote steam_request_conn_status() into steam_get_conn_status(). It\nkept the guard(mutex) line and added a call to\nsteam_exchange_report_id(), which takes the same mutex. Deadlock on its\nown: yes.\n\ngit blame shows the guard line as coming from de435b770cd94 only\nbecause the line survived the rewrite unchanged. The nested call, which\nis what makes it a deadlock, was added in 0a80b4e8ec6a6. The fix removes\nthe inner guard and leaves the caller holding the mutex.\n\n3. Bug 2: unlocked publish/clear of the input, sensor and battery\n   pointers (the \"RCU\" part)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe register and unregister helpers do an unlocked check followed by an\nupdate:\n\n    input \u003d rcu_dereference(steam-\u003einput);   // check\n    if (input) return 0;\n    ... input_register_device(input);\n    rcu_assign_pointer(steam-\u003einput, input); // act\n\nThe unregister side does the same: read the pointer, set it to NULL,\nsynchronize_rcu(), then input_unregister_device(). Battery and sensors\nfollow the same pattern, and steam-\u003eregistered is checked at :1385 but\nonly set at :1418.\n\nThis is only safe while one serialized caller runs these functions at a\ntime. Each time that stopped being true:\n\n - c164d6abf3841 (\"HID: add driver for Valve Steam Controller\", v4.18),\n   the original driver. Introduced the pattern. Probe registered only\n   wired controllers; wireless ones were registered only from the\n   work_connect worker. Effectively one caller at a time.\n\n - 385a4886778f6 (\"HID: steam: remove input device when a hid client is\n   running.\", v4.20). The hidraw open/close callbacks called\n   steam_input_unregister() and steam_input_register() directly,\n   concurrently with work_connect. This was the first race of this kind.\n\n - 79504249d7e27 (\"HID: hid-steam: Move hidraw input (un)registering to\n   work\", v6.14). Moved those calls into a separate unregister_work.\n   That work item could still run in parallel with work_connect.\n\n - cd33a91d37eb4 (\"HID: steam: Fully unregister controller when hidraw\n   is opened\", v7.3-rc1). Removed unregister_work and routed everything\n   through the single work_connect. This closed the earlier race.\n\n - 6f9b740c31ba7 (\"HID: steam: Refactor registration\"), the commit just\n   before 0a80b4e8ec6a6. Probe still called steam_register() only for\n   wired devices, with connected \u003d true set beforehand. A wired\n   controller sends no connect events, so the worker could never be\n   scheduled during probe. Removal cancels the worker before\n   unregistering.\n\nSo immediately before 0a80b4e8ec6a6 there was no concurrent caller.\n0a80b4e8ec6a6 reintroduced one. For wireless receivers, probe now:\n\n 1. calls hid_hw_open(), so raw events start arriving;\n\n 2. sets steam-\u003econnected \u003d false;\n\n 3. runs the slow synchronous steam_get_conn_status();\n\n 4. then calls steam_register() itself (:1733).\n\nDuring step 3, several raw events call steam_do_connect_event(steam,\ntrue):\n\n - ID_CONTROLLER_WIRELESS connect;\n\n - ID_CONTROLLER_STATUS arriving before a battery is registered;\n\n - the Ibex REPORT_ID_INPUT, REPORT_ID_INPUT2 or REPORT_ID_BATTERY\n   reports arriving before the input device exists.\n\nThat flips connected and schedules work_connect, which runs\nsteam_register() in a worker thread at the same time as probe\u0027s own\ncall.\n\nWhat two concurrent steam_register() calls can do:\n\n - Input and sensor devices: both callers see steam-\u003einput \u003d\u003d NULL and\n   both register a device. The second rcu_assign_pointer overwrites the\n   first, which stays registered and is never unregistered. That leaked\n   device still points at steam through its driver data. steam is\n   devm-allocated, so it is freed on removal. Opening the leaked device\n   later (steam_input_open) or playing a force-feedback effect on it is\n   a use-after-free.\n\n - Battery: both callers write into the shared steam-\u003ebattery_desc. The\n   losing power_supply_register() fails on the duplicate name and calls\n   devm_kfree() on the name string, which the registered power supply is\n   still using.\n\n - Unregister: two concurrent unregisters would both see the same\n   non-NULL pointer and unregister the same device twice.\n\nOn the parent tree this race sits behind bug 1, which hangs the same\nprobe path first. Fixing bug 1 alone would have exposed it, which is why\nthe fix adds registration_mutex in the same patch.\n\nIndependent evidence: the Sashiko AI review of v4 patch 5/6 on lore\ndescribes this exact sequence (a connect event during enumeration queues\nwork_connect, which calls steam_register() while probe is still in\nsteam_get_conn_status()). The v4 cover letter also says \"This fixes a\ndeadlock that accidentally crept into the last version.\"\n\n4. Why the older commits are not the answer\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - The unlocked register/unregister pattern from c164d6abf3841 was safe\n   while it had one serialized caller.\n\n - The races from 385a4886778f6 and 79504249d7e27 are separate bugs in\n   older trees. cd33a91d37eb4 removed them before the fix. The fix\u0027s\n   mutex sits in steam_register()/steam_unregister(), so it would not\n   even cover the unregister_work path in v6.14-v7.2 trees. Those trees\n   also lack 0a80b4e8ec6a6, so they don\u0027t have this bug.\n\n - The remaining hunks are benign data races that predate the series:\n\n     - gamepad_mode toggled without the lock (from cd438e57dd05b\n       (\"HID: hid-steam: Add gamepad-only mode switched to by holding\n       options\"), 2023);\n\n     - unlocked client_opened reads in steam_raw_event() and the\n       lizard-mode parameter handler;\n\n     - the check of client_opened followed later by lizard-mode\n       commands, from 691ead124a0c3 (\"HID: hid-steam: Clean up\n       locking\").\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "fb47e6a8ac80d9c83327b6a6fcb9f5d8f86c24a9",
      "tree": "caee5f860176bffce89e9d1b45a7458e83a757ec",
      "parents": [
        "247fced16dfe16f1ac614ea092d62c8bf5a62c8f"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 17:23:00 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:17 2026 -0400"
      },
      "message": "CVE-2026-98942: Add .vulnerable file\n\nThe root cause is e9b55af47edf6 (\"sched_ext: Add topological CPU IDs\n(cids)\"), by Tejun Heo, first released in v7.2-rc1. That commit created\nthe cid tables and the way they are published. The fix 3a773220d39ba\n(\"sched_ext: Build the cid tables privately and publish them with\nRCU\"), first released in v7.3-rc1, removes that publishing scheme. So\nv7.2 and v7.2.y are affected.\n\nTejun\u0027s own first version of this fix says the same thing. Before\n3a773220d existed, he posted \"[PATCH 3/3] sched_ext: Guard the cid\nkfuncs against unallocated cid tables\"\n(\u003c20260720082605.1451945-4-tj@kernel.org\u003e), which carried:\n\n    Fixes: e9b55af47edf (\"sched_ext: Add topological CPU IDs (cids)\")\n\nAndrea Righi replied (\u003cal3tLtPZZkFjMveK@gpd4\u003e, the Closes: link in the\nfix) that a NULL check isn\u0027t enough:\n\n    \"scx_cid_arrays_alloc() publishes these pointers before\n    scx_cid_init() populates them, so a racing program can still\n    observe ... uninitialized cid_topo entries. This also applies to\n    the other enables, where the tables remain non-NULL while being\n    rewritten.\"\n\n3a773220d is the rework that came out of that thread. The final commit\nhas no Fixes: tag; the v1 patch\u0027s tag is the authoritative attribution.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix names three ways a racing TRACING or SYSCALL BPF program can\nsee the cid tables while they are being changed:\n\n 1. First enable publishes the pointers before filling them. A reader\n    can see zeroed entries or, in the kmalloc\u0027d tables, uninitialized\n    heap memory.\n\n 2. Re-enables rebuild the tables in place. A reader sees a\n    half-rebuilt mapping or a torn struct scx_cid_topo.\n\n 3. ops.init_cids() overrides rewrite them in place.\n\nThe fix:\n\n - builds a fresh struct scx_cid_tables privately on each root enable;\n\n - makes the globals __rcu and publishes them with rcu_assign_pointer()\n   only once the layout is final (scx_cid_publish_tables());\n\n - on root disable, unpublishes them inside cpus_read_lock() and frees\n   them after an RCU grace period (scx_cid_retire_tables());\n\n - adds NULL checks to the kfuncs, and adds guard(rcu)() to\n   scx_bpf_this_cid() and scx_bpf_task_cid().\n\n2. What e9b55af47edf6 introduced\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIt added kernel/sched/ext_cid.c, which later moved to\nkernel/sched/ext/cid.c in bba2c3615bd6c. The design comment says it\ndirectly: \"Pointers are published once on first enable and never\nrevoked.\"\n\na) Publish before fill\n----------------------\n\nscx_cid_arrays_alloc() allocates the tables and makes them visible\nimmediately:\n\n    cid_to_cpu \u003d kzalloc_objs(...);\n    cpu_to_cid \u003d kzalloc_objs(...);\n    cid_topo   \u003d kmalloc_objs(*scx_cid_topo, npossible, GFP_KERNEL); /* not zeroed */\n    ...\n    WRITE_ONCE(scx_cid_to_cpu_tbl, cid_to_cpu);\n    WRITE_ONCE(scx_cpu_to_cid_tbl, cpu_to_cid);\n    WRITE_ONCE(scx_cid_topo, cid_topo);\n    return 0;\n\nOnly after that does scx_cid_init() fill them: it writes -1 sentinels\ninto scx_cpu_to_cid_tbl, walks node -\u003e LLC -\u003e core assigning\nscx_cid_to_cpu_tbl[cid], scx_cpu_to_cid_tbl[ccpu] and scx_cid_topo[cid],\nthen fills the no-topology tail. WRITE_ONCE gives no ordering for the\nstores that follow it, so it is not a safe publication.\n\nb) Allocate once, rebuild in place\n----------------------------------\n\n\"if (scx_cid_to_cpu_tbl) return 0;\" means every later root enable\nrewrites the same live arrays. Disable never clears them, so between\nschedulers they keep the stale mapping. The fix also removes that\n(\"instead of reporting the stale last mapping\").\n\nc) A wrong safety assumption ----------------------------\n\nThe comment it added in scx_root_enable_workfn() reads:\n\n    \"Build the cid mapping before publishing scx_root. The cid kfuncs\n    dereference the cid arrays unconditionally once scx_prog_sched()\n    returns non-NULL...\"\n\nBut at this commit, scx_prog_sched(aux) (in ext_internal.h, with\nCONFIG_EXT_SUB_SCHED) does not look at scx_root first:\n\n    ops \u003d bpf_prog_get_assoc_struct_ops(aux);\n    if (likely(ops))\n            return rcu_dereference_all(ops-\u003epriv);\n    root \u003d rcu_dereference_all(scx_root);\n    ...\n\nops-\u003epriv is set inside scx_alloc_and_add_sched()\n(rcu_assign_pointer(ops-\u003epriv, sch)), and that runs before\nscx_cid_init() in the enable work function:\n\n    scx_root_enable_workfn()\n      sch \u003d scx_alloc_and_add_sched()\n        -\u003e ops-\u003epriv \u003d sch            (readers can now get sch)\n      cpus_read_lock()\n      scx_cid_init(sch)\n        scx_cid_arrays_alloc()\n          -\u003e WRITE_ONCE(tables)       (pointers visible, contents not)\n        ... fill tables ...\n      rcu_assign_pointer(scx_root, sch)\n\nCONFIG_EXT_SUB_SCHED is def_bool y whenever SCHED_CLASS_EXT \u0026\u0026 CGROUPS,\nso this is the default configuration. The same commit registers\nscx_bpf_cid_to_cpu, scx_bpf_cpu_to_cid and scx_bpf_cid_topo for\nBPF_PROG_TYPE_TRACING and BPF_PROG_TYPE_SYSCALL. Any such program\nassociated with the scheduler\u0027s struct_ops map through\nBPF_PROG_ASSOC_STRUCT_OPS therefore gets a non-NULL sch during the\nwhole window. It then reads the global table pointer with no further\ncheck:\n\n - Before the first allocation: the pointer is NULL, so\nREAD_ONCE(scx_cid_to_cpu_tbl)[cid] or READ_ONCE(scx_cid_topo)[cid]\ndereferences NULL. This is what Tejun\u0027s v1 patch was trying to fix.\n\n - Between WRITE_ONCE and fill: scx_bpf_cid_topo() copies\n   scx_cid_topo[cid] into the program\u0027s buffer. That memory came from\n   kmalloc and is uninitialized, so kernel heap contents leak to BPF.\n   The mapping tables return zeros, which look like valid cids and CPUs.\n\n - On any later enable: the arrays are overwritten in place (-1\n   sentinels, then the new mapping). Readers can get inconsistent\n   cpu\u003c-\u003ecid pairs, or a torn 24-byte scx_cid_topo struct.\n\nOnly the design in e9b55af47edf6 makes all three possible: one global,\nlock-free set of tables, published before it is built and mutated in\nplace for its whole lifetime.\n\n3. Later commits widened it but didn\u0027t create it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - df7b5ae038d6f (\"Add scx_bpf_cid_override() kfunc\", same series,\n   v7.2-rc1) added in-place rewrites from ops.init(). That runs after\n   scx_root is published, so unassociated programs and builds without\n   sub-scheds can also see the rewrite. This is the origin of complaint\n   #3, but it relies on the published-and-mutable global tables from\n   e9b55af47edf6. The init-time override was later moved into\n   ops.init_cids() (874fdc0e86e4d, v7.3-rc1), which is why the fix\u0027s\n   commit message names that op.\n\n - 5ba0a42423335 (\"Add cid-form kfunc wrappers alongside cpu-form\",\n   v7.2-rc1) added scx_bpf_this_cid() and scx_bpf_task_cid(). They skip\n   scx_prog_sched() entirely and trust any non-NULL scx_cpu_to_cid_tbl,\n   with no RCU protection. These are \"the two kfuncs that were neither\"\n   in the fix\u0027s message. They are new readers of the broken publishing\n   scheme, not its source.\n\n - e2c841b91bf96 (\"Add CID sharding\", v7.3-rc1) added more tables built\n   the same way, including another kmalloc\u0027d one (shard_node).\n\n - bba2c3615bd6c only moved the file.\n\nNothing earlier than e9b55af47edf6 exists to trace further back: that\ncommit created the tables, their global pointers, the publishing order,\nand the comment that misread scx_prog_sched().\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "247fced16dfe16f1ac614ea092d62c8bf5a62c8f",
      "tree": "a5f795415394e2148d243a0604b0d2b0d3a8d90c",
      "parents": [
        "4256241647438db86a02d7ab390fa35637ca3dc3"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 17:16:07 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98937: Add .vulnerable file\n\nThe root cause is 0764771dab80 (\"perf_counter: More paranoia\nsettings\"), by Peter Zijlstra, 2009-06-11, first released in\nv2.6.31-rc1.\n\nThat commit created the rule that unprivileged users may not profile\nthe kernel, but it enforced the rule only through attr.exclude_kernel.\nKernel callchain sampling already existed then, and it ignored that\nattribute. The callchain path has leaked kernel data past the check ever\nsince. The fix has no Fixes: tag; the right one would be:\n\n    Fixes: 0764771dab80 (\"perf_counter: More paranoia settings\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\na4573a3838ae (patch 9 of Dapeng Mi\u0027s series, merged via\nperf-core-2026-08-17) widens the privilege gate in perf_event_open():\n\n    -       if (!attr.exclude_kernel) {\n    +       if (!attr.exclude_kernel ||\n    +           ((attr.sample_type \u0026 PERF_SAMPLE_CALLCHAIN) \u0026\u0026\n    +            !attr.exclude_callchain_kernel)) {\n                    err \u003d perf_allow_kernel();\n\nperf_allow_kernel() returns -EACCES when perf_event_paranoid \u003e 1 and\nthe caller lacks CAP_PERFMON. Level 2 has been the default since\n0161028b7c8a (v4.6). Before the fix, an unprivileged caller could set\nexclude_kernel\u003d1, skip the gate, and still request\nPERF_SAMPLE_CALLCHAIN with exclude_callchain_kernel\u003d0.\n\n2. How the leak happens (current code)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. With exclude_kernel\u003d1 on a hardware event, the counter only counts\n    user mode (x86 sets EVENTSEL_USR but not EVENTSEL_OS).\n\n 2. The overflow interrupt (PMI, an NMI on x86) arrives with skid. If\n    the counter overflows just before a syscall, exception or\n    interrupt entry, the NMI lands after the CPU is already in ring 0.\n    The regs passed in are kernel registers.\n\n 3. The x86 handlers pass those registers straight through:\n    perf_event_overflow(event, \u0026data, regs) in\n    arch/x86/events/core.c:1752 and arch/x86/events/intel/core.c:3930.\n    The single-record PEBS path builds the callchain from the\n    interrupt regs too (perf_sample_save_callchain(data, event, iregs)\n    at arch/x86/events/intel/ds.c:2207 and :2424).\n\n 4. perf_prepare_sample() calls perf_sample_save_callchain(), then\n    perf_callchain(), which sets\n    kernel \u003d !event-\u003eattr.exclude_callchain_kernel. That is true by\n    default, and nothing looks at exclude_kernel.\n\n 5. get_perf_callchain() runs\n    \"if (kernel \u0026\u0026 !user_mode(regs)) perf_callchain_kernel(\u0026ctx, regs);\".\n    That unwinds the kernel stack and writes kernel return addresses\n    into the ring buffer, which the unprivileged user reads through\n    mmap. The result is a KASLR break and a view of kernel control\n    flow.\n\nSoftware, tracepoint and breakpoint events filter kernel-mode hits with\nexclude_kernel, so they don\u0027t leak this way. The hole is the\nhardware-PMI skid path. The sibling patch 166f10836a65 (\"Fix kernel\nregister info leak via hardware skid\") fixes the same skid problem for\nPERF_SAMPLE_IP and PERF_SAMPLE_REGS_INTR. a4573a3838ae closes it for\ncallchains, at open time.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore June 2009 there was no kernel-profiling restriction. The parent\nof 0764771dab80 had only sysctl_perf_counter_priv, from \"perf_counter:\nsysctl for system wide perf counters\", and it applied only to CPU-wide\ncounters (find_get_context()). Any user could profile the kernel.\nKernel callchains were allowed by design, so there was no privilege\nboundary to cross.\n\nCallchain sampling came first, on 2009-03-30, also in the v2.6.31 merge\nwindow:\n\n - 394ee07623cf (\"perf_counter: provide generic callchain bits\")\n - d7d59fb32383 (\"perf_counter: x86: callchain support\")\n\nAt 0764771dab80 the x86 code looked like this:\n\n    perf_do_callchain(struct pt_regs *regs, ...)\n            is_user \u003d user_mode(regs);\n            ...\n            if (!is_user)\n                    perf_callchain_kernel(regs, entry);   /* dump_trace() of the kernel stack */\n            if (current-\u003emm)\n                    perf_callchain_user(regs, entry);\n\nThe kernel stack was walked whenever the sample regs were in kernel\nmode, with no check of exclude_kernel and no opt-out.\nintel_pmu_handle_irq() and amd_pmu_handle_irq() set data.regs \u003d regs\nstraight from the NMI.\n\n0764771dab80 then added the kernel restriction with an incomplete check:\n\n    + *  2 - disallow kernel profiling to unpriv\n    ...\n    +       if (!attr.exclude_kernel) {\n    +               if (perf_paranoid_kernel() \u0026\u0026 !capable(CAP_SYS_ADMIN))\n    +                       return -EACCES;\n    +       }\n\nIts stated goal was \"restrict kernel-mode code counting and profiling\".\nIt assumed exclude_kernel\u003d1 means no kernel data reaches the sample. For\ncallchains that was false from day one, because the capture path keyed\non user_mode(regs) and hardware skid produces kernel regs for user-only\nevents.\n\nThe same tree shows the gap was specific to the hardware path. The\nhrtimer software path in kernel/perf_counter.c already substituted user\nregs:\n\n    /* In case we exclude kernel IPs ... provide the next best thing, the user IP. */\n    if ((counter-\u003eattr.exclude_kernel || !data.regs) \u0026\u0026 !counter-\u003eattr.exclude_user)\n            data.regs \u003d task_pt_regs(current);\n\nThe hardware PMI path, which feeds callchains, got nothing equivalent.\n\nBlame confirms the gate is still the 2009 line. Running\n\"git blame -C -C\" on the parent of the fix attributes\n\"if (!attr.exclude_kernel) {\" and its closing brace to 0764771dab80\n(kernel/perf_counter.c). Later commits only rewrote the body:\n\n - cdd6c482c9ff9 (v2.6.32): the counter-to-event rename.\n - da97e18458fb4 (v5.5): perf_allow_kernel(\u0026attr) with LSM hooks.\n - 5e9629d0ae977 (2024): moved perf_allow_kernel() out of line and\n   exported it.\n - 9ec84f79c5a7a (2024-25): dropped the attr parameter.\n\nNone of these changed which requests go through the check.\n\n4. Commits ruled out\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - d077526485d5 (\"perf: Add attribute to filter out callchains\"), v3.7,\n   2012. It added exclude_callchain_kernel and exclude_callchain_user\n   and changed \"if (!user_mode(regs))\" to \"if (kernel \u0026\u0026\n   !user_mode(regs))\". The new bit defaults to 0, so behaviour stayed\n   exactly as before. It added an opt-out, which the fix now forces\n   unprivileged users to set; it did not create the leak, which existed\n   three years earlier.\n\n - 394ee07623cf / d7d59fb32383 (callchain support): when they landed\n   there was no kernel-profiling privilege boundary to bypass. The bug\n   appeared once a boundary existed and didn\u0027t cover them.\n\n - cc1582c231ea (\"perf/core: Drop kernel samples even though :u is\n   specified\"), v4.12-rc5. It dropped whole samples when exclude_kernel\n   \u0026\u0026 !user_mode(regs), which blocked this leak. Ingo\nMolnar reverted it in 6a8a75f32357 (v4.13-rc2) because it broke rr. The\nrevert brought back the 2009 behaviour rather than creating a new bug.\nIt does show the skid leak was known in 2017.\n\n5. Summary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 0764771dab80 (\"perf_counter: More paranoia settings\"),\n   v2.6.31-rc1. It introduced the perf_counter_paranoid \u003e\u003d 2 rule but\n   enforced it only via !attr.exclude_kernel, even though\nPERF_SAMPLE_CALLCHAIN already emitted kernel stacks whenever a sample\u0027s\nregs were in kernel mode. Skid makes that routine for user-only hardware\nevents.\n\n - Affected range: every kernel from v2.6.31 until a4573a3838ae, apart\n   from the v4.12-rc5 to v4.13-rc2 window when cc1582c231ea was in\n   place.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "4256241647438db86a02d7ab390fa35637ca3dc3",
      "tree": "2c6333c595adfde4295c331c52ef10bd9a50df79",
      "parents": [
        "0af8a5e547e70b2eefe1714eeb93644cb4d22779"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 17:01:43 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98926: Add .vulnerable file\n\nThe root cause is 052fa7e8c9a92e (\"drm/amd/display: reset\nretimer/redriver below 340Mhz\"), by Charlene Liu, first released in\nv5.3-rc1. It added the first unconditional stream-\u003esink dereference to\nthe DPMS path. It did this about five months after stream-\u003esink had been\nofficially deprecated and removed from that path.\n\nSuggested tag:\n\n    Fixes: 052fa7e8c9a9 (\"drm/amd/display: reset retimer/redriver below 340Mhz\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n8fa813b7fd1e (\"drm/amd/display: Remove sink usage from DPMS\"), by\nDominik Kaszewski, landed in v7.3-rc1 and has no Fixes: tag. It works\non link_set_dpms_on() and link_set_dpms_off() in\ndrivers/gpu/drm/amd/display/dc/link/link_dpms.c. Its stated reason is:\n\n    stream-\u003esink is optional and can be null, so should always be\n    checked before dereference. Additionally, most of its usage in\n    DPMS sequences is for stream-\u003esink-\u003elink, which can be replaced\n    with stream-\u003elink, as the two should always be the same.\n\nIt removes three unguarded stream-\u003esink dereferences:\n\n A. link_set_dpms_off(), code before the fix:\n\n        struct dc_link *link \u003d stream-\u003esink-\u003elink;\n\n    Came from 052fa7e8c9a92 (v5.3-rc1).\n\n B. link_set_dpms_off(), code before the fix:\n\n        if (!pipe_ctx-\u003estream-\u003esink-\u003eedid_caps.panel_patch.skip_avmute)\n\n    Came from 74d021b5631f0 (v5.11-rc1).\n\n C. link_set_dpms_on(), code before the fix:\n\n        struct dc_link *link \u003d stream-\u003esink-\u003elink;\n\n    Came from 3550d6225b1ff (v5.16-rc1).\n\nA and C now use stream-\u003elink, and B gets a \"stream-\u003esink \u0026\u0026\" guard. The\nrest of the fix is cleanup: a get_vpg() helper, and pipe_ctx-\u003estream-\u003e\nreplaced with stream-\u003e.\n\nTwo details make A and C unconditional crashes when the sink is NULL:\n\n - The \"link \u003d stream-\u003esink-\u003elink\" line is a declaration initializer.\nIt runs before the dc_is_virtual_signal() early return and before the\nexisting \"if (pipe_ctx-\u003estream-\u003esink)\" logging guard, so neither of\nthose protects it.\n\n - link is used straight away by\nASSERT(is_master_pipe_for_link(link, ...)), dp_get_panel_mode(link) and\nget_link_hwss(link, ...).\n\nThese functions are reached from every commit and DPMS path through\ndc-\u003elink_srv-\u003eset_dpms_on/off: dc.c, dce110_hwseq.c, and the\ndcn10/20/21/31/401 reset_back_end_for_pipe sequences.\n\n2. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nCode move, not the cause\n------------------------\n\ngit blame on the fix\u0027s parent points every line at 54618888d1ea7\n(\"drm/amd/display: break down dc_link.c\"), by Wenjing Liu, v6.3-rc1.\nThat commit moved core_link_enable_stream()/core_link_disable_stream()\nfrom core/dc_link.c into link_dpms.c and renamed them, unchanged.\nBlaming dc_link.c at 54618888d1ea7^ gives the real origins below.\n\nThe contract: ceb3dbb4690db\n---------------------------\n\nceb3dbb4690db (\"drm/amd/display: remove sink reference in\ndc_stream_state\"), by Jun Lei, v5.1-rc1. This commit:\n\n - added \"struct dc_link *link\" and \"struct dc_panel_patch\n   sink_patches\" to dc_stream_state, filled in construct() from the\n   sink:\n\n       stream-\u003elink \u003d dc_sink_data-\u003elink;\n       stream-\u003esink_patches \u003d dc_sink_data-\u003eedid_caps.panel_patch;\n\n - replaced every stream-\u003esink-\u003elink in the enable/disable paths with\n   stream-\u003elink. That covered core_link_disable_stream()\u0027s\n   disable_link(pipe_ctx-\u003estream-\u003esink-\u003elink, ...), the\n   link_enc-\u003efuncs-\u003esetup and cur_link_settings uses in\n   core_link_enable_stream(), and the enable_link_* and i2c retimer\n   helpers;\n\n - documented the rule in dc_stream.h:\n\n       // sink is deprecated, new code should not reference\n       // this pointer\n       struct dc_sink *sink;\n\nThe commit message also says DC \"should be perfectly capable of\nprogramming hardware without any dc_sinks (for example, emulated\nsinks).\"\n\nAfter ceb3dbb4690db, neither DPMS function touched stream-\u003esink at all.\nThis was checked with awk/grep on the file at that commit and at\n052fa7e8c9a92e^.\n\nThe bug: 052fa7e8c9a92e\n-----------------------\n\n052fa7e8c9a92e was written in April 2019 and released in v5.3-rc1;\nceb3dbb4690db is its ancestor. To add HDMI retimer/redriver reset on\nstream disable, it changed core_link_disable_stream() like this:\n\n         struct dc_stream_state *stream \u003d pipe_ctx-\u003estream;\n    +    struct dc_link *link \u003d stream-\u003esink-\u003elink;\n     ...\n    -            dal_ddc_service_write_scdc_data(\n    -                    stream-\u003elink-\u003eddc, 0, ...\n    +            unsigned short masked_chip_caps \u003d link-\u003echip_caps \u0026 ...\n    +            dal_ddc_service_write_scdc_data(\n    +                    link-\u003eddc, 165000, false);\n\nIt replaced the safe stream-\u003elink-\u003eddc with a new local taken from the\ndeprecated pointer. It also placed that dereference at the top of the\nfunction, so it runs for every signal type and every stream, not only\nHDMI. This is exactly line A, carried unchanged into\nlink_set_dpms_off().\n\nLater copies of the same pattern\n--------------------------------\n\n - 74d021b5631f0 (\"drm/amd/display: skip avmute action\"), v5.11-rc1,\n   read skip_avmute through\n   pipe_ctx-\u003estream-\u003esink-\u003eedid_caps.panel_patch. The deprecation-safe\n   copy, stream-\u003esink_patches, already existed. This is line B.\n\n - 3550d6225b1ff (\"drm/amd/display: Add DPCD writes at key points\"),\n   v5.16-rc1, a revert of a revert, added the identical\n\"struct dc_link *link \u003d stream-\u003esink-\u003elink;\" to\ncore_link_enable_stream(), just for dp_source_sequence_trace(link, ...).\nThis is line C.\n\nEvidence the NULL case was known\n--------------------------------\n\nfa39f936dbb96 (\"drm/amd/display: add dsc mst stream pbn log for\ndebug\"), v5.18-rc1, wrapped only the new DC_LOG_DC in \"if\n(pipe_ctx-\u003estream-\u003esink)\" in both functions, and left A, B and C\nunguarded a few lines away. The follow-up fix 55a67aa893425 quotes this\ninconsistency as proof that the sink can legitimately be NULL.\n\nWhy not go back further?\n------------------------\n\nBefore ceb3dbb4690db, the original 2017 DC code did use\nstream-\u003esink-\u003elink in these functions. But stream-\u003elink did not exist\nthen, and the sink was the required, by-design route to the link, so\nthose uses were not this bug. The defect the fix describes only becomes\npossible once stream-\u003elink exists and the sink is declared optional.\n052fa7e8c9a92 is the first commit to break that rule in DPMS.\n\n3. Caveat on reachability\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIn today\u0027s Linux tree, every dc_stream_state comes from\ndc_create_stream_for_sink(), which returns NULL for a NULL sink. That\ncovers amdgpu_dm and the SubVP phantom streams in dc_state.c. The only\nother source is dc_copy_stream(), which copies the sink pointer. So no\nway was found to get a NULL stream-\u003esink through amdgpu_dm. The defect\nis that DC code shared with other display managers breaks its own\ndocumented contract. That matters if reachability needs to be argued for\nthis CVE.\n\n4. Related\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix itself kept stream-\u003esink-\u003elink in allocate_usb4_bandwidth*()\nbehind only ASSERT(), which is compiled out in production builds. That\nwas fixed later by 55a67aa893425 (\"drm/amd/display: Fix NULL sink\ndereference in USB4 bandwidth functions\"), which carries\n\"Fixes: 8fa813b7fd1e\" and Cc stable. It is a separate follow-up, not\npart of this root cause.\n\n5. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe disable-path crash exists from v5.3. The enable-path dereference\nhas existed since v5.16.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "0af8a5e547e70b2eefe1714eeb93644cb4d22779",
      "tree": "588ab7213eedf38bcec3c82e21e37686b1641f0d",
      "parents": [
        "8b9d8d8babadc94debf518cd0ff20814c891a001"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:53:34 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98917: Add .vulnerable file\n\nThe root cause is da57c5414f49e (\"parisc: Reduce thread stack to 16\nkb\"), by John David Anglin, first released in v4.15-rc5. It made every\nparisc kernel built with CONFIG_IRQSTACKS\u003dy use a 16 KB task stack.\nThat option is on by default, so 64-bit kernels got the 16 KB stack\ntoo. The fix undoes that commit\u0027s change for 64-bit only.\n\nSuggested tag:\n\n    Fixes: da57c5414f49 (\"parisc: Reduce thread stack to 16 kb\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n94b7e3a7e871a (\"parisc: Increase kernel stack size to 32kb\"), by Helge\nDeller, landed in v7.3-rc5. It has no Fixes: tag and carries\n\"Cc: stable@vger.kernel.org # v6.18+\". It changes one line in\narch/parisc/include/asm/thread_info.h:\n\n    -#ifdef CONFIG_IRQSTACKS\n    +#if defined(CONFIG_IRQSTACKS) \u0026\u0026 !defined(CONFIG_64BIT)\n     #define THREAD_SIZE_ORDER\t2 /* PA-RISC requires at least 16k stack */\n     #else\n     #define THREAD_SIZE_ORDER\t3 /* PA-RISC requires at least 32k stack */\n\nBefore the fix, a 64-bit kernel with IRQ stacks had THREAD_SIZE \u003d\nPAGE_SIZE \u003c\u003c 2, which is 16 KB. After the fix it has 32 KB. The 32-bit\nkernel stays at 16 KB.\n\nThe patch history on lore points straight at the culprit:\n\n - v1 (\u003c20260919201744.3057-1-deller@kernel.org\u003e) simply deleted the\n\"#ifdef CONFIG_IRQSTACKS / 16k / #else / #endif\" lines. Those are\nexactly the lines da57c5414f49e added, so v1 was a plain revert of it.\n\n - v2 (\u003c20260921202122.3102-1-deller@kernel.org\u003e, the version that was\n   merged) limited the revert to CONFIG_64BIT.\n\nNobody replied to either thread.\n\n2. How the crash happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe panic was reproduced on an rp3440 running 6.18.46 while it built\nDebian\u0027s vtk9 package:\n\n    stackcheck: kworker/u128:0 will most likely overflow kernel stack\n      (sp:179a83af0, stk bottom-top:179a80000-179a84000)\n    Workqueue: writeback wb_workfn (flush-259:0)\n    ... do_cpu_irq_mask+0x3f8 \u003c- intr_return\n\nThe sequence is:\n\n 1. The deep call chain. The writeback kworker runs wb_workfn and goes\n    down through the filesystem, the block layer and the SCSI/storage\n    driver. All of this runs on the task\u0027s own kernel stack. On parisc\n    that stack grows upward (config STACK_GROWSUP), so usage is\n    sp - task_stack_page(current).\n\n 2. The interrupt arrives. An external interrupt enters through\n    intr_extint in entry.S. Even with CONFIG_IRQSTACKS, the register\n    save happens first, on the stack that was interrupted. The full\n    pt_regs and a frame are pushed onto the kworker\u0027s task stack before\n    any switch. Then do_cpu_irq_mask() runs, still on the task stack,\n    and calls stack_overflow_check(regs). Only after that does\n    execute_on_irq_stack() move to the per-CPU IRQ stack. The revert\n    9352aeada4d8d describes the same thing: \"the register saves are\n    done on the thread stack prior to switching to the IRQ stack.\"\n\n 3. The check trips. In arch/parisc/kernel/irq.c,\n    stack_overflow_check() computes:\n\n     - usage \u003d 0x179a83af0 - 0x179a80000 \u003d 0x3af0 \u003d 15088 bytes\n     - limit \u003d THREAD_SIZE - STACK_MARGIN \u003d 16384 - 1536 \u003d 14848 bytes\n     - 15088 \u003e 14848, so it prints the message and panics\n       (sysctl_panic_on_stackoverflow \u003d 1).\n     - With a 32 KB stack the limit is 31232 bytes, and the same chain\n       would have used under half of it.\n\n 4. Without the debug check, it\u0027s worse. CONFIG_DEBUG_STACKOVERFLOW is\n    set in generic-64bit_defconfig, so the result here was a clean\n    panic (a DoS). The check is only a sample taken when an external\n    interrupt arrives. parisc has no HAVE_ARCH_VMAP_STACK, so there are\n    no guard pages. If the check is off or doesn\u0027t happen to fire, an\n    overflow silently writes past the top of the order-2 allocation\n    into whatever physical memory comes next.\n\nWhy 64-bit in particular\n------------------------\n\nIn the 64-bit PA-RISC ABI everything on the stack is about twice the\nsize of the 32-bit version (arch/parisc/include/asm/assembly.h):\n\n                              64-bit   32-bit\n    FRAME_SIZE                   128       64\n    REG_SZ                         8        4\n    CALLEE_REG_FRAME_SIZE        144      128\n\npt_regs is also built from 8-byte registers. So the same call chain\nuses roughly twice as much stack on a 64-bit kernel. A 16 KB stack on\n64-bit holds about as much as an 8 KB stack would on 32-bit. That is why\nv2 of the fix only changes CONFIG_64BIT.\n\n3. Full history of THREAD_SIZE_ORDER on parisc\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 1da177e4c3f41 (2.6.12-rc2): THREAD_ORDER 2 (16 KB) for all configs.\n   No IRQ stacks existed yet.\n\n - 200c880420a2c, 416821d3d6816, d96b51ec14650 (v3.10): IRQ stacks\n   added (16 KB IRQ stack), with CONFIG_IRQSTACKS defaulting to y.\n\n - 8f8201dfed91a (v4.13-rc4): \"Increase thread and stack size to 32kb\".\n   Task and IRQ stacks both go to 32 KB in every config, because\n\"Since kernel 4.11 the thread and irq stacks on parisc randomly\noverflow the default size of 16k\" (Cc stable 4.11+).\n\n - 9352aeada4d8d (v4.15-rc5): Revert of \"parisc: Re-enable interrupts\n   early\" (5c38602d83e5, v4.10). Interrupts nesting on the task stack\n   had been one source of the overflows.\n\n - da57c5414f49e (v4.15-rc5): \"Reduce thread stack to 16 kb\".\n   An \"#ifdef CONFIG_IRQSTACKS\" selects THREAD_SIZE_ORDER 2.\n\n - 31680c1d1595a (v5.12-rc1): \"Bump 64-bit IRQ stack size to 64 KB\",\n   because \"The 64-bit stack size needs to be larger than the 32-bit\n   stack size since registers are twice as big.\" Only the IRQ stack was\n   changed.\n\n - 94b7e3a7e871a (v7.3-rc5): the fix. The 64-bit task stack goes back\n   to 32 KB.\n\n4. Why da57c5414f49e is the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe whole commit is this hunk:\n\n    +#ifdef CONFIG_IRQSTACKS\n    +#define THREAD_SIZE_ORDER\t2 /* PA-RISC requires at least 16k stack */\n    +#else\n     #define THREAD_SIZE_ORDER\t3 /* PA-RISC requires at least 32k stack */\n    +#endif\n\nIts only justification: \"In testing, I found that the thread stack can\nbe 16 kB when using an irq stack.\" It came in the parisc-4.15-2 pull\n(merge cb81fc6a3cf69) next to the 9352aeada4d8d revert. The pull\nmessage says that with the early-interrupt change reverted, \"we can\nlower our thread stack back to 16kb again.\"\n\nFour points make it the commit that introduced the bug:\n\n - It went back to a size already known to overflow. One release\n   earlier, 8f8201dfed91a had moved every config to 32 KB because 16 KB\n   task stacks were overflowing. da57c5414f49e cut that in half again\n   for the default config, based only on testing.\n\n - It treated 32-bit and 64-bit the same. Although every 64-bit frame\n   and register save is twice as large, the #ifdef only looks at\n   CONFIG_IRQSTACKS. The missing !CONFIG_64BIT condition is exactly what\n   the fix adds.\n\n - IRQ stacks don\u0027t protect the task stack from deep calls. They take\n   hardirq and softirq handler depth off the task stack. They do nothing\n   for a deep process-context chain like writeback -\u003e fs -\u003e block -\u003e\n   driver. They also don\u0027t cover the pt_regs and frame that the\n   interrupt entry pushes onto the task stack before switching. So\n\"has IRQ stacks\" was never a sound reason to give the task 16 KB on\n64-bit.\n\n - The maintainers later saw the 64-bit problem but only fixed half of\n   it. 31680c1d1595a (v5.12) doubled the 64-bit IRQ stack because\n   \"registers are twice as big\". It left the 64-bit task stack at the\n16 KB set by da57c5414f49e. That is context, not the cause. Since then,\ncall chains and compiler-generated frames have kept growing, and they\nfinally went past the 14848-byte limit.\n\n5. Ruled-out candidates\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nTwo other commits might look like candidates. Both were ruled out:\n\n - 1da177e4c3f41 (the original 16 KB in 2.6.12-rc2) is only history.\n8f8201dfed91a had already removed that 16 KB setting. The code the fix\nchanges was written by da57c5414f49e.\n\n - The v6.18+ stable tag reflects where the problem was reproduced and\n   how far back it is being backported. Every parisc commit from v6.12\n   to v6.19 was checked, and none of them changes stack sizes or\n   interrupt entry. The undersized 64-bit stack has been present in\n   every kernel since v4.15.\n\n6. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n64-bit parisc kernels with CONFIG_IRQSTACKS\u003dy (the default), from v4.15\nthrough v7.3-rc4.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "8b9d8d8babadc94debf518cd0ff20814c891a001",
      "tree": "782a22005a82f98593dc74082b8550dd891e7775",
      "parents": [
        "f4bd786e2deab1c16b41d85eadca0b8efcea6b97"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:47:27 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98916: Add .vulnerable file\n\nThe defect was introduced by 0d2ab3aea50bb (\"nl80211: add support for\nBSS coloring\"), by John Crispin, July 2021, first released in v5.15-rc1.\nIt first became reachable with b4b9f0a32d31 (\"mt76: mt7915:\nintroduce bss coloring support\"), by Lorenzo Bianconi, first released\nin v5.16-rc1. That is the first in-tree driver to set\nNL80211_EXT_FEATURE_BSS_COLOR, which nl80211_color_change() requires,\nso it is the commit recorded in .vulnerable (see section 6).\n\n0d2ab3aea50bb created nl80211_color_change() without the \"is this AP\nactually beaconing?\" check. Every other AP beacon-modifying handler in\nthe same file already had that check. The fix 55308cee9c21 (\"wifi:\nnl80211: check link is beaconing for color change\") adds exactly this\nmissing check, now spelled per link.\n\nThe matching tag for the fix stays on the code origin:\n\n    Fixes: 0d2ab3aea50b (\"nl80211: add support for BSS coloring\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix makes two changes in nl80211_color_change():\n\n    +\tparams.link_id \u003d nl80211_link_id(info-\u003eattrs);\n    +\tif (!wdev-\u003elinks[params.link_id].ap.beacon_interval)\n    +\t\treturn -EINVAL;\n     \terr \u003d nl80211_parse_beacon(rdev, info-\u003eattrs, \u0026params.beacon_next,\n     \t\t\t\t   wdev-\u003elinks[params.link_id].ap.chandef.chan, ...);\n     ...\n    -\tparams.link_id \u003d nl80211_link_id(info-\u003eattrs);\n     \terr \u003d rdev_color_change(rdev, dev, \u0026params);\n\n 1. The real change. It rejects the request unless the target link has\n    beacon_interval !\u003d 0. cfg80211 sets that field only after a\n    successful start_ap and clears it in __cfg80211_stop_ap(), so it\n    means \"this AP is running\". For non-MLO interfaces link_id is 0, so\n    the check also applies to them.\n\n 2. A reorder. link_id is now computed before nl80211_parse_beacon().\n    That function only needed it from 3967fe47e1d1b onward (see the\n    second false lead in section 5).\n\n2. Why the missing check matters\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nnl80211_color_change() only requires NL80211_FLAG_NEED_NETDEV_UP, plus\nNL80211_FLAG_MLO_VALID_LINK_ID since v6.10. An interface can be AP-type\nand up without START_AP ever having been called: hostapd does this\nduring setup, and anyone with CAP_NET_ADMIN in the netns can do it with\niw/ip link. The command uses GENL_UNS_ADMIN_PERM, so CAP_NET_ADMIN in\nthe user namespace that owns the netns is enough.\n\nThe only real implementer of .color_change is mac80211\u0027s\nieee80211_color_change(). Its first version (5f9404abdf2ac, the mac80211\nhalf of the same series) never checks for an existing beacon.\nToday\u0027s version doesn\u0027t either. Compare mac80211\u0027s own\nieee80211_change_beacon(), which returns -ENOENT when there is no beacon\n(net/mac80211/cfg.c:2088).\n\nOn an AP link that isn\u0027t running, this happens:\n\n - ieee80211_set_color_change_beacon() saves the request\u0027s next-beacon\n   data into link-\u003eu.ap.next_beacon. It then calls\n   ieee80211_assign_beacon(), which refuses only when there is no head\n*and* no old beacon. If the request carries a head, it installs a\nbrand-new link-\u003eu.ap.beacon on a link that was never started.\n\n - color_change_active is set to true, and userspace is told the change\n   has started.\n\n - ieee80211_color_change_bss_config_notify() calls\n   drv_link_info_changed(..., BSS_CHANGED_BEACON |\nBSS_CHANGED_HE_BSS_COLOR). The driver is now told to update the beacon\nof a link it never started: this is the \"invalid link\" in the fix\u0027s\ncommit message. With count \u003c\u003d 1, ieee80211_color_change_finalize() runs\nstraight away and makes the same calls.\n\n - After that the interface is stuck:\n\n    - A later START_AP passes cfg80211\u0027s beacon_interval check but fails\n      in ieee80211_start_ap() with -EALREADY, because a beacon now\n      exists (net/mac80211/cfg.c:1789).\n\n    - ieee80211_stop_ap() is the only function that frees u.ap.beacon,\n      and cfg80211 never calls it because it requires\n      beacon_interval !\u003d 0 (net/wireless/ap.c:30). The beacon buffers\n      leak.\n\n    - The countdown never finishes, since no beacons go out, so\n      color_change_active stays set and later CSA or color-change\n      requests get -EBUSY.\n\n3. How 0d2ab3aea50bb introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAt that commit, nl80211.c already guarded all its sibling handlers on\nwdev-\u003ebeacon_interval:\n\n - nl80211_set_mac_acl() (line 4554):\n\n       if (!...-\u003ebeacon_interval)\n               return -EINVAL;\n\n - nl80211_set_beacon() (line 5560):\n\n       if (!wdev-\u003ebeacon_interval)\n               return -EINVAL;\n\n - nl80211_channel_switch() (line 9206), present since 16ef1fe272332\n   (v3.12):\n\n       /* useless if AP is not running */\n       if (!wdev-\u003ebeacon_interval)\n               return -ENOTCONN;\n\n - nl80211_color_change() (new): none. It only checks for the op,\nEXT_FEATURE_BSS_COLOR, iftype \u003d\u003d AP and the attributes, then calls\nrdev_color_change().\n\nColor change was modelled closely on CSA: count,\nNL80211_ATTR_CNTDWN_OFFS_BEACON/PRESP, and the \"beacon during / beacon\nafter\" pair. It copied the offset validation but not CSA\u0027s \"AP is\nrunning\" gate. In v5.15 the missing line would have been:\n\n    if (!wdev-\u003ebeacon_interval)\n            return -EINVAL;\n\nThe field became per-link in 7b0a0e3c3a882 (v6.0), which is why the fix\nreads wdev-\u003elinks[link_id].ap.beacon_interval.\n\n4. Nothing in between fixed it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on nl80211_color_change() shows dc1e3cb8da8b4 (MBSSID),\ndbbb27e183b15 (RNR), 076fc8775dafe (wdev mutex removal), 91d2b6ee137dc\n(link id), c932be7262323 (unsolicited probe response) and the treewide\nkmalloc_obj changes. On the mac80211 side it shows eb87d3e08992b,\nd0a9123ef548d, 15ddba5f43114, 0cd8080e46b83, 414e736c3db03 and\nf7130c9e3e125. None adds a beaconing or beacon-present check, so the gap\nran unbroken from 0d2ab3aea50bb until the fix.\n\n5. Two false leads\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n91d2b6ee137dc (\"wifi: cfg80211: handle color change per link\",\nv6.10-rc1). Its mac80211 half is 414e736c3db03. Running blame on the\nline the fix moved lands here, because this commit added params.link_id\n\u003d nl80211_link_id(...) and NL80211_FLAG_MLO_VALID_LINK_ID. It is not the\nroot cause:\n\n - MLO_VALID_LINK_ID only checks that the link is in wdev-\u003evalid_links.\nIt never checked whether the link is beaconing, so the commit carried\nthe existing gap into per-link form.\n\n - The fix\u0027s check also changes non-MLO behaviour (link 0), which was\n   vulnerable before link IDs existed.\n\n3967fe47e1d1b (\"wifi: nl80211: always validate AP operation/PHY\nregulatory\"). This is four commits earlier in the same series and the\nsame v7.2-rc1 merge. It made nl80211_parse_beacon() dereference\nchan-\u003eflags whenever the beacon has HE, EHT or UHR operation, which a\ncolor-change beacon always has. It passes\nwdev-\u003elinks[params.link_id].ap.chandef.chan while link_id was still 0:\n\n - On a stopped AP, that pointer is NULL, so this is a NULL dereference.\n\n - On an MLD where link 0 isn\u0027t an AP link, it is also NULL even when\n   the target link is beaconing.\n\nThat is why the fix also moves the link_id assignment. But this\nregression only made the old gap crash sooner, and it was never in a\nrelease because the fix landed alongside it.\n\n6. Code origin vs. vulnerable boundary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nSince 0d2ab3aea50bb added it, nl80211_color_change() has rejected every\nwiphy that doesn\u0027t advertise the feature flag, before it looks at the\nAP at all:\n\n    if (!wiphy_ext_feature_isset(\u0026rdev-\u003ewiphy,\n                                 NL80211_EXT_FEATURE_BSS_COLOR))\n            return -EOPNOTSUPP;\n\nSo the bug needs two ingredients: the handler without the beaconing\ncheck, and a driver that sets the flag. Only drivers set it.\n\n - In v5.15 the only reference to NL80211_EXT_FEATURE_BSS_COLOR outside\n   the uapi header is that check in net/wireless/nl80211.c. No driver\n   and nothing in mac80211 sets it, so NL80211_CMD_COLOR_CHANGE returns\n-EOPNOTSUPP on every in-tree wiphy. The latest 5.15 stable release,\nv5.15.223, has no setter either and no backport of b4b9f0a32d31.\n\n - git log -S\u0027NL80211_EXT_FEATURE_BSS_COLOR\u0027 over drivers/ and net/\n   lists 0d2ab3aea50bb, then b4b9f0a32d31 (mt7915, v5.16-rc1), then\n886433a98425 (\"ath11k: add support for BSS color change\", v5.17-rc1).\nb4b9f0a32d31 calls wiphy_ext_feature_set(wiphy,\nNL80211_EXT_FEATURE_BSS_COLOR) unconditionally in mt7915_init_wiphy(),\nand mt7915 supports\n   NL80211_IFTYPE_AP.\n\n - The failure is reachable at b4b9f0a32d31. Both 0d2ab3aea50bb and its\n   mac80211 half 5f9404abdf2ac are ancestors of it, and at that commit:\n\n    - nl80211_color_change() has no beacon_interval check.\n\n    - ieee80211_color_change() only refuses when color_change_active or\n      csa_active is already set.\n\n    - With count \u003e 1, ieee80211_set_color_change_beacon() duplicates\n      the next beacon into sdata-\u003eu.ap.next_beacon and calls\n      ieee80211_assign_beacon(), which returns -EINVAL only when\n      !params-\u003ehead \u0026\u0026 !old. A request with a head installs\n      sdata-\u003eu.ap.beacon, sets color_change_active and notifies the\n      driver with BSS_CHANGED_BEACON | BSS_CHANGED_HE_BSS_COLOR.\n\n    - With count \u003c\u003d 1, ieee80211_color_change_finalize() runs straight\n      away. It clears color_change_active, but\n      ieee80211_set_after_color_change_beacon() installs next_beacon\n      through ieee80211_assign_beacon() in the same way and the driver\n      gets the same notification.\n\n    - ieee80211_start_ap() still returns -EALREADY when u.ap.beacon is\n      set, and __cfg80211_stop_ap() still returns -ENOENT when\n      wdev-\u003ebeacon_interval is 0. The beacon therefore can\u0027t be freed\n      and the AP can\u0027t be started, exactly as described in section 2.\n\nb4b9f0a32d31 is therefore the boundary where the kernel became\nvulnerable, and it goes in .vulnerable. 0d2ab3aea50bb is still the code\norigin and the Fixes: target. This moves the affected floor from v5.15\nto v5.16-rc1 and takes the 5.15.y stable tree out of the affected set.\n\n7. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nv5.16-rc1 through v7.1.x, with the fix in v7.2-rc1. v5.15 and the\n5.15.y stable tree contain the defective handler, but no in-tree driver\nthere can reach it. Only drivers that set NL80211_EXT_FEATURE_BSS_COLOR\nare exposed: mt7915 (from v5.16-rc1), ath11k (from v5.17-rc1), ath12k,\nmt7996 and mac80211_hwsim.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "f4bd786e2deab1c16b41d85eadca0b8efcea6b97",
      "tree": "5b7246f97a100a104b94c0c16150f601986249e7",
      "parents": [
        "e24b2cce5890d2299e32499e28caf97060fb1803"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:34:28 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98915: Add .vulnerable file\n\nThe bug was introduced by 5cebaac60974 (\"media: video-i2c: add\nvideo-i2c driver\"), by Matt Ranostay, first released in v4.18. The\npattern the fix corrects (vb2_fop_release() as the release handler, with\nonly video_unregister_device() in remove) has been there since the\ndriver was first added. Later commits changed the code around it but\nnever touched the cause.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n56384b486b80 changes one line in video_i2c_remove():\n\n    -\tvideo_unregister_device(\u0026data-\u003evdev);\n    +\tvb2_video_unregister_device(\u0026data-\u003evdev);\n\nThe fix has no Fixes: tag. Its message says that a driver using\nvb2_fop_release() must use vb2_video_unregister_device(), so the vb2\nqueue is disconnected when the device goes away.\n\n2. The bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe driver sets .release \u003d vb2_fop_release\n(drivers/media/i2c/video-i2c.c:384). It captures frames with a kthread\nthat start_streaming() creates and only stop_streaming() stops. Two\nframework rules matter here:\n\n 1. vb2_fop_release() is the only place this driver\u0027s queue is torn\n    down. _vb2_fop_release() (videobuf2-v4l2.c:1152) calls\n    vb2_queue_release(), and through it stop_streaming(), only when the\n    closing file handle owns the queue. So streaming stops when the\n    owner closes its file descriptor, not when the device is removed.\n\n 2. video_unregister_device() does not stop streaming. It clears\n    V4L2_FL_REGISTERED, wakes event waiters and calls\n    device_unregister() (v4l2-dev.c:1122). It never touches\n    vdev-\u003equeue. After it runs, every ioctl on the open file returns\n    -ENODEV (v4l2_ioctl(), v4l2-dev.c:359). Userspace therefore can\u0027t\n    even issue VIDIOC_STREAMOFF; closing the file is the only way out.\n\nWhat happens before the fix:\n\n 1. A process opens /dev/videoN, calls REQBUFS/QBUF/STREAMON (or just\n    read(), since VB2_READ is supported) and becomes the queue owner.\n\n 2. start_streaming() takes a runtime-PM reference, runs\n    chip-\u003esetup(), and starts video_i2c_thread_vid_cap().\n\n 3. The device goes away: sysfs unbind, delete_device, I2C adapter\n    removal (for example a USB-I2C bridge being unplugged) or a DT\n    overlay removal. Plain rmmod is blocked because the open file holds\n    a module reference.\n\n 4. video_i2c_remove() disables runtime PM, powers the sensor off with\n    chip-\u003eset_power(data, false), and calls video_unregister_device().\n    The remove callback returns while the capture thread is still\n    running.\n\n 5. The orphaned thread keeps calling data-\u003echip-\u003exfer(), which does\n    I2C reads:\n\n     - Today it uses regmap_bulk_read(). The regmap keeps a raw\n       \u0026client-\u003edev and takes no reference on it.\n\n     - In the original driver it called\n       i2c_transfer(data-\u003eclient-\u003eadapter, ...).\n\n     - Once the client is unregistered, device_del() drops its\n       reference on the adapter (drivers/base/core.c:3984).\n       i2c_del_adapter() can then finish and the adapter can be freed,\n       while the thread still reaches it through client-\u003eadapter. That\n       is a use-after-free.\n\n     - Even in the mild case, the thread keeps talking to hardware that\n       is powered off and has no bound driver. If the device is\n       rebound, it races the new driver instance on the same chip.\n\n 6. Only when the owner finally closes the file does vb2_fop_release()\n    -\u003e stop_streaming() -\u003e kthread_stop() run, followed by\n    pm_runtime_put_autosuspend(regmap_get_device(...)) on a device that\n    was unbound long ago. If the process never closes it, the thread\n    runs indefinitely.\n\nvb2_video_unregister_device() takes a reference on the vdev,\nunregisters it, then, under the queue lock, calls vb2_queue_release()\nfor the queue owner. That stops the thread and returns the buffers\nbefore remove returns.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n\"git log --follow\" on drivers/media/i2c/video-i2c.c lists 39 commits.\nOnly these touch the relevant code:\n\n - 5cebaac60974 \"media: video-i2c: add video-i2c driver\", v4.18.\nIntroduces it. A new 564-line file, not moved from anywhere. It already\nhas .release \u003d vb2_fop_release, the kthread started and stopped only by\nstart_streaming/stop_streaming, and a remove that only calls\nvideo_unregister_device(), then v4l2_device_unregister() and\nmutex_destroy(). Nothing stops streaming. Worse, the immediate\nv4l2_device_unregister() dropped the client reference taken by\nv4l2_device_register() (get_device(dev)), so the client could be freed\nwhile the thread dereferenced data-\u003eclient.\n\n - c764da98a600 \"media: video-i2c: avoid accessing released memory area\n   when removing driver\", v5.0. A different use-after-free: fields of\n   data used after the vdev release freed it. It moved\n   v4l2_device_unregister() and mutex_destroy() into video_i2c_release()\n   and left video_unregister_device() alone, so streaming still survives\n   removal. It carries\n\"Fixes: 5cebaac60974\", which confirms the remove path was flawed from\nthe first commit.\n\n - ae9e196dad3b \"media: video-i2c: use i2c regmap\", v5.0. xfer now goes\n   through regmap instead of data-\u003eclient. It changes how the stale\n   thread reaches the hardware, not whether it can.\n\n - 69d2a734c5dc \"media: video-i2c: support runtime PM\", v5.0. Adds\n   runtime PM in start/stop streaming and power-off in remove. This adds\n   symptoms (sensor powered off under the running thread, PM put on an\n   unbound device), not the cause.\n\n - f729ef5796d8 \"media: videobuf2-v4l2.c: add\n   vb2_video_unregister_device helper function\" (Hans Verkuil), v5.10.\n   Adds the helper the fix uses. Its message describes this exact gap:\nwithout it, streaming continues until the owner closes the file. It\nchanges nothing in video-i2c.\n\n - 56384b486b80 (the fix), v7.2. Switches to the helper.\n\n4. Why the root cause isn\u0027t f729ef5796d8\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThat commit didn\u0027t create the bug in video-i2c; it added the standard\ntool for fixing it. From v4.18 to v5.10 the driver could have stopped\nstreaming itself, for example by calling vb2_queue_release() under\nqueue-\u003elock in remove, which is what the helper does internally. The\ndefect is a capture thread that outlives remove(), and that has been\npresent since the driver was first added.\n\nThe matching tag for the fix would be:\n\n    Fixes: 5cebaac60974 (\"media: video-i2c: add video-i2c driver\")\n\nEvery stable tree from v4.18 onward is affected.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e24b2cce5890d2299e32499e28caf97060fb1803",
      "tree": "88858a387e3001a9b15a78e17bdd01bf06c39d6d",
      "parents": [
        "ce81e06ddec0a0c0c7e3bcc967f69c743a108090"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:28:31 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98914: Add .vulnerable file\n\nVulnerable from: a2e73f56fa628 (\"drm/amdgpu: Add support for CIK\nparts\"), by Alex Deucher, 2015-04-20, first in v4.2-rc1. That is the\nfirst commit where amdgpu can bind to a GPU and get far enough through\namdgpu_device_init() to execute AtomBIOS command tables, so it is where\nthe unbounded CALL_TABLE recursion first becomes reachable (see section\n5).\n\nThe defective code comes from d38ceaf99ed01 (\"drm/amdgpu: add core\ndriver (v4)\"), by Alex Deucher, 2015-04-20, also first in v4.2-rc1.\nThat commit created drivers/gpu/drm/amd/amdgpu/atom.c, the amdgpu file\nthis fix touches, with the unbounded CALL_TABLE recursion already in it.\nAt that commit, though, the driver has an empty PCI ID table and\namdgpu_early_init() rejects every ASIC before the VBIOS is even read, so\nit is the defect origin rather than the vulnerable boundary. The design\nitself is older still: amdgpu\u0027s atom.c is a copy of radeon\u0027s, and the\nrecursion first appeared in 771fe6b912fca (v2.6.31-rc1, 2009).\nRadeon\u0027s copy is still uncapped in origin/master.\n\nThe fix commit carries no Fixes: tag; the matching one names the code\norigin:\n\n    Fixes: d38ceaf99ed0 (\"drm/amdgpu: add core driver (v4)\")\n\nd38ceaf99ed01, a2e73f56fa628 and the PCI ID commits that follow them\nare all in v4.2-rc1, so the affected-version floor is v4.2 whichever one\nis recorded.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n27ef1795bc4e4 (\"drm/amdgpu: cap ATOM command table nesting depth\",\nv7.2-rc1) adds \"unsigned int execute_depth\" to struct atom_context:\n\n - amdgpu_atom_execute_table_locked() now increments it on entry and\n   decrements it on every exit through the \"free:\" label.\n\n - It refuses to go past ATOM_EXECUTE_MAX_DEPTH \u003d 32 and returns -ELOOP\n   instead.\n\n - The caller, atom_op_calltable(), turns any non-zero return into\n   ctx-\u003eabort \u003d true, so every level above unwinds cleanly.\n\nThe patch\u0027s own comment says why: the cap exists \"to avoid kernel stack\noverflow\".\n\n2. The bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAtomBIOS command tables are bytecode in the VBIOS image, and the kernel\nruns them in an interpreter. Opcode 0x52 (CALL_TABLE) is implemented as\na real C recursion on the kernel stack\n(drivers/gpu/drm/amd/amdgpu/atom.c:635):\n\n    static void atom_op_calltable(atom_exec_context *ctx, int *ptr, int arg)\n    {\n        int idx \u003d U8((*ptr)++);  /* table index \u003d a byte read from the VBIOS */\n        ...\n        if (U16(ctx-\u003ectx-\u003ecmd_table + 4 + 2 * idx))\n            r \u003d amdgpu_atom_execute_table_locked(ctx-\u003ectx, idx, ...);  /* recursion */\n\namdgpu_atom_execute_table_locked() (atom.c:1224) builds a new\natom_exec_context ectx on the stack, allocates a per-level WS buffer of\nup to 255x4 bytes, and runs the dispatch loop. That loop calls\nopcode_table[op].func, which can be atom_op_calltable again. Before the\nfix nothing limited the depth:\n\n - The VBIOS chooses the callee. idx is a raw byte from the image. A\n   table that calls its own index, or two tables that call each other,\n   recurses forever. The ROM is supposed to form a shallow tree, but the\n   kernel never checked that.\n\n - Each level costs real stack. There are two C frames per level: one\n   holds the roughly 64-byte ectx plus locals and saved registers, and\n   atom_op_calltable adds its own. That is on the order of 150-200 bytes\n   per level (an estimate, not a measurement). On a 16 KiB stack that is\n   already partly used by the probe or resume path, a cycle overflows\n   after roughly 100 levels.\n\n - When it runs. It starts at probe and resume through\n   amdgpu_atom_asic_init() -\u003e amdgpu_atom_execute_table(ctx,\n   ATOM_CMD_INIT, ...) (atom.c:1638, amdgpu_atomfirmware.c:1117).\n   Display, I2C and DP-AUX paths use the same interpreter.\n\n - Result. With CONFIG_VMAP_STACK the overflow hits the guard page and\n   the kernel panics. Without it, the overflow silently corrupts\n   whatever memory sits below the stack.\n\n - Who can trigger it. Whoever controls the VBIOS image: a malicious or\n   reflashed board, an eGPU over Thunderbolt, a passthrough device\u0027s ROM\nBAR, or a tampered VFCT/ATRM table. All of these are read by\namdgpu_get_bios() in amdgpu_bios.c.\n\n3. Why the existing safeguards never fired\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - The \"stuck\" watchdog (last_jump / last_jump_jiffies, originally\n   c21b0fe6de391) only fires when the same jump target repeats within\n   one ectx for longer than ATOM_CMD_TIMEOUT_SEC. Recursion through\nCALL_TABLE executes no jump, and each level starts with a fresh ectx\nwhere last_jump \u003d 0.\n\n - ectx.abort is only checked by the parent after the callee returns.\n   In an infinite recursion the callee never returns.\n\n - debug_depth is a global that only controls indentation of debug\n   output. It is never compared against a limit. It is the only \"depth\"\n   symbol \"git log -S depth\" finds in either atom.c before the fix.\n\n - 4630d5031cd87 (\"drm/amdgpu: check PS, WS index\", 2024) bounds PS/WS\n   array indices, not nesting. A table with PS\u003d0 passes the same ps_size\n   down every level.\n\n4. History, traced back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 771fe6b912fca (\"drm/radeon: introduce kernel modesetting...\"),\n   v2.6.31-rc1, by Jerome Glisse, interpreter by Stanislaw Skowronek.\nCreated radeon/atom.c: atom_op_calltable -\u003e\natom_execute_table(ctx-\u003ectx, idx, ...) with no depth limit. This is\nwhere the design originates.\n\n - c31ad97f1886a (\"prevent parallel AtomBIOS calls\"), v2.6.33-rc2.\nAdded the mutex and redirected the recursion to the new\natom_execute_table_locked so it would not deadlock on itself. The\nrecursion was kept, still unbounded.\n\n - c21b0fe6de391 (\"catch atombios infinite loop...\"), v2.6.34-rc4.\nAdded the jump watchdog, the abort flag and int returns. Nothing about\ndepth.\n\n - d38ceaf99ed01 (\"drm/amdgpu: add core driver (v4)\"), v4.2-rc1.\nCreated amdgpu/atom.c (--diff-filter\u003dA) as a copy of the radeon\ninterpreter of that time. atom_op_calltable -\u003e\namdgpu_atom_execute_table_locked is there byte-for-byte, with no cap.\n   Code origin, but unreachable (section 5).\n\n - a2e73f56fa628 (\"drm/amdgpu: Add support for CIK parts\"), v4.2-rc1.\n   First commit where the interpreter can run on real hardware.\n   Vulnerable boundary.\n\n - 4630d5031cd87, cc9a8e238e42c, 7bfd16d0ec374, and about 40 others,\n   v4.3 ... v7.1. Every commit touching amdgpu atom.c/atom.h was\n   checked. None added or removed a depth limit; \"git log -L\" on\n   atom_op_calltable shows only d38ceaf99ed01 and 4630d5031cd87 (which\n   only added the ps_size argument).\n\n - 27ef1795bc4e4 (the fix), v7.2-rc1. Adds execute_depth and the cap of\n   32.\n\n5. Code origin vs. vulnerable boundary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe amdgpu bring-up series landed as consecutive commits, all dated\n2015-04-20 and all in v4.2-rc1:\n\n    d38ceaf99ed01  drm/amdgpu: add core driver (v4)\n    17b10f941f939  drm/amdgpu: fix const warnings in amdgpu_connectors.c\n    18da4340e651d  drm/amdgpu: Do not directly dereference pointers to BIOS area.\n    a2e73f56fa628  drm/amdgpu: Add support for CIK parts\n    aaa36a976bbb9  drm/amdgpu: Add initial VI support\n    89330c391b407  drm/amdgpu: add CIK pci ids\n    1256a8b89eeaf  drm/amdgpu: add VI pci ids\n\nd38ceaf99ed01 describes itself as \"the non-asic specific core driver\ncode\", and at that commit the interpreter cannot be reached:\n\n - pciidlist[] in amdgpu_drv.c holds only the {0, 0, 0} terminator, so\n   no device matches the static ID table.\n\n - Writing to the driver\u0027s sysfs new_id does not help. store_new_id()\n   only accepts a driver_data that matches an existing id_table entry,\n   and an empty table has none, so the ID is refused with -EINVAL.\n\n - driver_override (PCI, v3.16+) does bind the driver.\n   pci_match_device() hands probe the dummy pci_device_id_any, whose\n   driver_data is 0.\namdgpu_pci_probe() passes that through drm_get_pci_dev() and\namdgpu_driver_load_kms() to amdgpu_device_init() as flags \u003d 0, so\nasic_type \u003d 0 \u003d CHIP_BONAIRE.\n\n - amdgpu_device_init() calls amdgpu_early_init() right after mapping\n   the MMIO and I/O BARs, before amdgpu_get_bios(),\n   amdgpu_atombios_init() and amdgpu_atom_asic_init(). At d38ceaf99ed01\n   amdgpu_early_init() is only\n\n       switch (adev-\u003easic_type) {\n       default:\n           /* FIXME: not supported yet */\n           return -EINVAL;\n       }\n\n   so every probe fails before the VBIOS is read. Resume\n(amdgpu_resume_kms() -\u003e amdgpu_atom_asic_init()) and\namdgpu_boot_test_post_card() both need a device that finished init, and\nno DRM device exists to issue ioctls against. No command table can\nexecute.\n\n - 17b10f941f939 and 18da4340e651d only touch amdgpu_connectors.c and\n   amdgpu_bios.c. Neither changes amdgpu_early_init() or the ID table.\n\na2e73f56fa628 is the first commit where the path is live:\n\n - It adds CIK objects to the Makefile under CONFIG_DRM_AMDGPU_CIK\n   (cik.o, dce_v8_0.o, gmc_v7_0.o, ...) and, under the same option,\n\"case CHIP_BONAIRE/HAWAII/KAVERI/KABINI/MULLINS:\" to\namdgpu_early_init(), which calls cik_set_ip_blocks().\n\n - The ID table is still empty there, but a driver_override bind\n   arrives with driver_data 0 \u003d CHIP_BONAIRE. cik_set_ip_blocks()\n   accepts it (bonaire_ip_blocks), amdgpu_early_init() succeeds, and\n   amdgpu_device_init() goes on to amdgpu_get_bios() and\n   amdgpu_atombios_init().\n\n - If the card is not posted, amdgpu_atom_asic_init() executes\nATOM_CMD_INIT right away. If it is posted, amdgpu_init() still runs the\nDCE block\u0027s hw_init, and dce_v8_0.c calls\namdgpu_atombios_encoder_init_dig(), which executes command tables\nthrough amdgpu_atom_execute_table(). Either way the CALL_TABLE recursion\nruns on a VBIOS-supplied table.\n\nLater commits in the same series only widen the reach. 89330c391b407\nadds CIK PCI IDs, so CIK parts bind without driver_override when\nCONFIG_DRM_AMDGPU_CIK\u003dy. aaa36a976bbb9 and 1256a8b89eeaf build VI\nsupport unconditionally and add Topaz/Tonga/Carrizo IDs, so the path is\nlive in a default configuration. All of them are in v4.2-rc1 like\na2e73f56fa628 and d38ceaf99ed01, so the affected-version floor is the\nsame.\n\n6. Why not 771fe6b912fca\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\namdgpu\u0027s atom.c is a copy of radeon\u0027s, not a move; radeon\u0027s file still\nexists and still recurses without a cap\n(drivers/gpu/drm/radeon/atom.c:641 in origin/master). The fix touches\nonly amdgpu\u0027s copy. 771fe6b912fca never touched that file, and kernels\nv2.6.31 through v4.1 have no amdgpu driver, so they cannot be affected\nby this CVE.\n\nThe bug entered the patched code with d38ceaf99ed01, became reachable\nwith a2e73f56fa628, and was never changed afterwards. The affected range\nis v4.2-rc1 up to (not including) v7.2-rc1. The earlier\nVBIOS-hardening fix from the same author, CVE-2026-97493, recorded\nd38ceaf99ed01 itself. That commit cannot run the interpreter, so this\nrecord uses the first reachable commit instead. The version floor is the\nsame either way.\n\nRadeon: the same defect, inherited from 771fe6b912fca, is still unfixed\nand would need its own equivalent patch.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "ce81e06ddec0a0c0c7e3bcc967f69c743a108090",
      "tree": "107eb76d39e416d111cee5503374ab27b1271b0c",
      "parents": [
        "0a1a53f08c0d0d16874ddfbcf9ec24e90442feff"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:15:32 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98913: Add .vulnerable file\n\nVulnerable from: 4a8755096466d (\"net: thunderx: add timestamping\nsupport\"), by Sunil Goutham, merged in v4.16-rc1.\n\nThat commit added the cavium_ptp_get() call to nicvf_probe(). The call\ntakes a reference on the PTP PCI device. The commit released that\nreference only in nicvf_remove(), never on any failing exit from probe.\nNo later commit made the leak or fixed it; they only added more failing\nexits that inherited it.\n\nThe fix commit has no Fixes: tag; the matching one would be:\n\n    Fixes: 4a8755096466 (\"net: thunderx: add timestamping support\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, 2bcf59eefb9f0, changes\ndrivers/net/ethernet/cavium/thunder/nicvf_main.c in two places:\n\n - Early failure: if pci_enable_device() fails, probe used to return\n   straight away. It now jumps to a new err_put_ptp label.\n\n - New cleanup step: \"err_put_ptp: cavium_ptp_put(ptp_clock);\" sits at\n   the bottom of the existing cleanup chain, after err_disable_device.\n   Every failing exit from probe now passes through it.\n\nSo the bug is that a reference taken at the top of nicvf_probe() is\nnever dropped when probe fails.\n\n2. How the reference is taken and released\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ndrivers/net/ethernet/cavium/common/cavium_ptp.c:\n\n    struct cavium_ptp *cavium_ptp_get(void)\n    {\n        /* takes a ref on the pci_dev */\n        pdev \u003d pci_get_device(PCI_VENDOR_ID_CAVIUM,\n                              PCI_DEVICE_ID_CAVIUM_PTP, NULL);\n        if (!pdev)\n            return ERR_PTR(-ENODEV);\n        ptp \u003d pci_get_drvdata(pdev);\n        if (!ptp)\n            ptp \u003d ERR_PTR(-EPROBE_DEFER);\n        if (IS_ERR(ptp))\n            pci_dev_put(pdev);  /* ref dropped only on the error return */\n        return ptp;             /* success: ref is KEPT */\n    }\n\n    void cavium_ptp_put(struct cavium_ptp *ptp)\n    {\n        if (!ptp) return;\n        /* ptp-\u003epdev was set in cavium_ptp_probe (clock-\u003epdev \u003d pdev) */\n        pci_dev_put(ptp-\u003epdev);\n    }\n\n - pci_get_device() returns the device with its reference count raised.\n\n - On success, cavium_ptp_get() deliberately keeps that reference. The\n   caller now owns it until it calls cavium_ptp_put().\n\n - The PTP device here is the separate on-SoC PTP coprocessor (Cavium\n   device ID 0xA00C), not the VF being probed.\n\nThis API came from 8c56df372bc13 (\"net: add support for Cavium PTP\ncoprocessor\"), the patch just before the root-cause commit in the same\nseries (same date, both v4.16-rc1). The API itself is correct: it pairs\na get with a put and cleans up after its own failures. The leak is in\nthe caller.\n\n3. How 4a8755096466d introduced the leak\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe commit added this to the top of nicvf_probe():\n\n    +    struct cavium_ptp *ptp_clock;\n    +\n    +    ptp_clock \u003d cavium_ptp_get();\n    +    if (IS_ERR(ptp_clock)) {\n    +        if (PTR_ERR(ptp_clock) \u003d\u003d -ENODEV)\n    +            /* In virtualized environment we proceed without ptp */\n    +            ptp_clock \u003d NULL;\n    +        else\n    +            return PTR_ERR(ptp_clock);\n    +    }\n     ...\n    +    nic-\u003eptp_clock \u003d ptp_clock;\n\nThe only matching release it added was in nicvf_remove():\n\n    +    cavium_ptp_put(nic-\u003eptp_clock);\n         free_netdev(netdev);\n\nIt did not touch the probe error paths. Reading nicvf_probe() as of\n4a8755096466d, every failing exit after a successful cavium_ptp_get()\nkeeps the reference:\n\n - Failure point: pci_enable_device()\n   Exit path at 4a8755096466d: dev_err(); return err;\n   PTP ref released? No\n\n - Failure point: pci_request_regions()\n   Exit path at 4a8755096466d: goto err_disable_device\n   PTP ref released? No\n\n - Failure point: pci_set_dma_mask / pci_set_consistent_dma_mask\n   Exit path at 4a8755096466d: goto err_release_regions\n   PTP ref released? No\n\n - Failure point: alloc_etherdev_mqs()\n   Exit path at 4a8755096466d: goto err_release_regions\n   PTP ref released? No\n\n - Failure point: pcim_iomap() / drv_stats alloc /\n   nicvf_set_qset_resources() / nicvf_register_misc_interrupt()\n   Exit path at 4a8755096466d: goto err_free_netdev\nPTP ref released? No; free_netdev() frees nic, which holds the only\nstored copy (nic-\u003eptp_clock)\n\n - Failure point: nicvf_set_real_num_queues() / register_netdev()\n   Exit path at 4a8755096466d: goto err_unregister_interrupts\n   PTP ref released? No\n\nThe cleanup chain the commit left in place:\n\n    err_unregister_interrupts: nicvf_unregister_interrupts(nic);\n    err_free_netdev:           pci_set_drvdata(NULL);\n                               free_percpu(drv_stats);\n                               free_netdev(netdev);\n    err_release_regions:       pci_release_regions(pdev);\n    err_disable_device:        pci_disable_device(pdev);\n                               return err;  /* no cavium_ptp_put() */\n\nSo the commit acquired a resource and freed it only on the\nsuccess/teardown path. That is exactly the asymmetry the fix closes.\n\nOne path does not leak: in QS-only mode (\"if (nic-\u003esqs_mode) return\n0;\") probe succeeds, and nicvf_remove() drops the reference later. Only\nfailed probes are affected.\n\n4. Tracing back: no code movement hid an older origin\n   \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - git log -S\u0027cavium_ptp_get\u0027 over drivers/net/ethernet/cavium/ finds\n   only 8c56df372bc13 (API added) and 4a8755096466d (the probe call).\n\n - git log -S\u0027cavium_ptp_put\u0027 over the same directory finds only\n8c56df372bc13, 4a8755096466d and the fix. No commit ever added a put to\nthe probe error path and later removed it.\n\n - Before 4a8755096466d, nicvf_probe() made no PTP call at all, so\n   there was nothing to leak.\n\n - git log -L on nicvf_probe() between the root-cause commit and the\n   fix shows 11 commits:\n\n     - 37c3347eb247a, 469998c861faa and 2ecbe4f4a0278 (rx_mode\n       workqueue)\n     - 609ea65c65a0f and c1055b76ad00a (mailbox mutex)\n     - 0b31d98d90f09 (NULL check)\n     - 1e0dd56e962e2 (pci_ to dma_ API)\n     - 52583c8d8b12f (dev_err_probe)\n     - 42330a32933fb (the err_destroy_workqueue label)\n     - 66c0e13ad236c and 3c249fe4de160 (xdp_features)\n\nNone of them changed PTP reference handling. Some added new failing\nexits (for example alloc_ordered_workqueue() failure and the\nerr_destroy_workqueue label), and each of those simply inherited the\nmissing put.\n\n5. Impact\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Every failed nicvf_probe() raises the PTP pci_dev reference count by\n   one, permanently.\n\n - A realistic trigger is nicvf_register_misc_interrupt() returning\n   -EIO when the PF does not answer the READY mailbox message\n(nicvf_check_pf_ready()). MSI-X allocation failure, -ENOMEM and\nregister_netdev() failure also trigger it. Each VF that fails, and each\nretry such as unbind/bind, leaks one more reference.\n\n - With a leaked reference the count never reaches zero. If the PTP\n   function is removed (for example via sysfs \"remove\"), its struct\n   pci_dev and embedded struct device are never freed.\n\n - This is a reference-count and memory leak only. It is not a\n   use-after-free, and it needs a probe failure, which an unprivileged\n   user normally cannot cause. Severity is low.\n\n6. Note for backports\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix calls cavium_ptp_put(ptp_clock) even when ptp_clock is NULL,\nwhich happens in the -ENODEV case on virtualized setups with no PTP\ndevice. That is only safe because 07a2e1cf39818 (\"net: cavium: fix NULL\npointer dereference in cavium_ptp_put\", v4.16-rc3) added the\n\"if (!ptp) return;\" check. Every tree that contains 4a8755096466d\n(v4.16+) also has 07a2e1cf39818, so the fix backports cleanly in that\nrespect.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "0a1a53f08c0d0d16874ddfbcf9ec24e90442feff",
      "tree": "2b00a217cc5e2083d5175d8f13ea70e91fdb022f",
      "parents": [
        "76ce9a33117b5cd5ce7924824e2a1cac4c7659c8"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:10:54 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98912: Add .vulnerable file\n\nVulnerable from: 1da177e4c3f41 (\"Linux-2.6.12-rc2\"), the initial git\nimport from April 2005. The inverted comparison in snd_seq_oss_read() is\nin that first snapshot word for word, and its meaning has never changed\nsince. The fix carries no Fixes: tag; the matching one would be:\n\n    Fixes: 1da177e4c3f4 (\"Linux-2.6.12-rc2\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, 611f538253d97, makes one change in snd_seq_oss_read()\n(sound/core/seq/oss/seq_oss_rw.c:60):\n\n    -        if (ev_len \u003c count) {\n    +        if (count \u003c ev_len) {\n    +                err \u003d -EINVAL;\n\nThe read loop works like this:\n\n - It runs while count \u003e\u003d SHORT_EVENT_SIZE (4 bytes).\n\n - Each pass peeks at the head of the read queue with\n   snd_seq_oss_readq_pick(), which copies the record but leaves it\n   queued.\n\n - It gets the event\u0027s size from ev_length() (seq_oss_event.h:92):\n   code \u003e\u003d 128 means an 8-byte event, anything else is a 4-byte event.\n\n - The size check is meant to stop when the event won\u0027t fit in the\n   buffer that\u0027s left. If the event fits, the loop dequeues it with\n   snd_seq_oss_readq_free(), copies ev_len bytes to the user, and\n   advances buf and count.\n\nThe check was written backwards. It stops when the event is *smaller*\nthan the remaining buffer and copies when the event is *larger*.\n\n2. What goes wrong\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n2a. Up to 4 bytes written past the user\u0027s buffer, and read() returns\n    more than was asked for --------------------------------------------------------------------\n\n 1. Open /dev/music (music mode) and write an 8-byte EV_TIMING event\n    with cmd \u003d TMR_ECHO.\n\n 2. timing_event() (seq_oss_event.c:228) passes that 8-byte record to\n    set_echo_event().\n\n 3. The echo comes back through snd_seq_oss_event_input() -\u003e\n    snd_seq_oss_readq_put_event(). The queue now holds an 8-byte\n    record with code EV_TIMING (0x81, which is \u003e\u003d 128). Music-mode\n    timestamps (snd_seq_oss_readq_put_timestamp) and music-mode MIDI\n    input also queue 8-byte records.\n\n 4. read(fd, buf, 4): the loop runs because 4 \u003e\u003d 4, and ev_len is 8.\n    \"8 \u003c 4\" is false, so the record is dequeued and\n    copy_to_user(buf, \u0026rec, 8) writes 8 bytes into a 4-byte buffer.\n\n 5. count becomes -4, the loop exits, and the call returns 8.\n\nvfs_read() only checks access_ok(buf, count) and doesn\u0027t limit what\n-\u003eread returns. copy_to_user() still keeps the write inside user address\nspace, so this overwrites user memory next to the buffer, not kernel\nmemory. The overrun is at most 4 bytes (8 minus 4), because the loop\nguard keeps count at 4 or more.\n\n2b. Normal-sized reads look like end-of-file\n--------------------------------------------\n\n - /dev/sequencer (synth mode) queues 4-byte events (SEQ_MIDIPUTC,\n   SEQ_WAIT, SEQ_ECHO). A read with an 8- or 1024-byte buffer hits\n   \"4 \u003c count\", breaks with err \u003d\u003d 0, and returns 0.\n\n - Reads only return data when the buffer is exactly the event\u0027s size,\n   or too small for it as in 2a.\n\n - The fix now returns -EINVAL when the buffer is too small, and leaves\n   the event queued for a later, larger read.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on snd_seq_oss_read() in origin/master shows only three\ncommits:\n\n - 1da177e4c3f41 (2005-04-16, \"Linux-2.6.12-rc2\") adds the function\n   with \"if (ev_len \u003c count)\" exactly as it was before the fix.\n\n - 080dece3460b5 (\"[ALSA] Remove xxx_t typedefs: Sequencer\n   OSS-emulation\", 2005-11-17) only renames types\n(seq_oss_devinfo_t -\u003e struct seq_oss_devinfo, evrec_t -\u003e union evrec).\nThe logic is the same.\n\n - 611f538253d97 is the fix.\n\nThe helpers it depends on mean the same thing in the 2.6.12-rc2 import\nas they do now:\n\n - SHORT_EVENT_SIZE is 4 and LONG_EVENT_SIZE is 8.\n\n - The ev_length() and ev_is_long() macros are identical.\n\n - snd_seq_oss_readq_pick() still only peeks at the queue head.\n\n - odev_read() still passes count straight through.\n\nNo refactor in between created or moved the bug. Later changes to the\nreadq files (da24e6b3c251d guard cleanup, 1595a5b8c53d2 evrec\nallocation, 49ce92d207820 locking) don\u0027t touch the size check.\n\n4. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 1da177e4c3f41 (\"Linux-2.6.12-rc2\"), 2005-04-16.\n\n - Suggested tag:\n\n    Fixes: 1da177e4c3f4 (\"Linux-2.6.12-rc2\")\n\n - Not the root cause:\n\n    - 080dece3460b5 only renamed typedefs.\n\n    - da24e6b3c251d, 1595a5b8c53d2 and 49ce92d207820 touched the readq\n      files but not the size check.\n\nThe code is older than git: the file header credits Takashi Iwai\u0027s\nALSA OSS sequencer emulation (1998-99), which was merged during the\n2.5 era. Within origin/master history, 1da177e4c3f41 is the furthest\nback it can be attributed, and every kernel since 2.6.12 is affected.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "76ce9a33117b5cd5ce7924824e2a1cac4c7659c8",
      "tree": "f4c324d3d57c0817684d42ce0f90785ad0d0a0d0",
      "parents": [
        "30c9caca6dabed1c6db93e000e1605196255e9ca"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 16:05:57 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98911: Add .vulnerable file\n\nVulnerable from: 613c0a5810b79 (\"staging: dpaa2-switch: enable the\ncontrol interface\"), by Ioana Ciornei, first released in v5.13-rc1.\nThat is the first commit where the driver turns on the DPSW control\ninterface (CTRL_IF) in firmware. It is therefore the first point where\nframes reach dpaa2_switch_rx() and its broken error path becomes\nreachable.\n\nThe defective code comes from 0b1b713704588 (\"staging: dpaa2-switch:\nhandle Rx path on control interface\"), by Ioana Ciornei, two commits\nearlier in the same series and also first released in v5.13-rc1. The\nerror path in dpaa2_switch_rx() was wrong from the first day the\nfunction existed, and no later commit added the defect. However, at\n0b1b713704588 the control interface is never enabled, so no frame can\nreach dpaa2_switch_rx(). That makes 0b1b713704588 the defect origin, not\nthe vulnerable boundary (see section 5). The Fixes: tag should still\nname the code origin.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix is 74c1c9f5c0c30 (\"dpaa2-switch: fix the error path in\ndpaa2_switch_rx()\"), v7.2-rc1. It has no Fixes: tag.\n\nBefore the fix, every error exit in dpaa2_switch_rx() jumped to\nerr_free_fd, which called dpaa2_switch_free_fd(ethsw, fd). That helper\nis built for Tx frame descriptors:\n\n    skbh \u003d dpaa2_iova_to_virt(ethsw-\u003eiommu_domain, fd_addr);\n    skb  \u003d *skbh;                    /* expects an skb back-pointer at buffer start */\n    dma_unmap_single(dev, fd_addr,\n                     skb_tail_pointer(skb) - buffer_start,  /* dereferences that \"skb\" */\n                     DMA_TO_DEVICE);                         /* Tx direction */\n    dev_kfree_skb(skb);\n\nThis only works for buffers prepared by the Tx side,\ndpaa2_switch_build_single_fd(). That function writes *skbh \u003d skb at the\nstart of the buffer and maps it with:\n\n    dma_map_single(..., skb_tail_pointer(skb) - buff_start, DMA_TO_DEVICE)\n\nRx buffers are built differently. dpaa2_switch_add_bufs() allocates\nbare pages with dev_alloc_pages(0) and maps them with:\n\n    dma_map_page(dev, page, 0, DPAA2_SWITCH_RX_BUF_SIZE, DMA_FROM_DEVICE)\n\nNo skb exists and nothing writes an skb pointer at the start of the\nbuffer, so the first 8 bytes are whatever the page already held.\n\nThe fix:\n\n - moves dpaa2_iova_to_virt() and dma_unmap_page() out of\n   dpaa2_switch_build_linear_skb() and into the start of\n   dpaa2_switch_rx();\n\n - frees the page with free_pages(vaddr, 0) on errors that happen\n   before an skb exists;\n\n - calls kfree_skb(skb) when __skb_vlan_pop() fails after the skb was\n   built.\n\n2. What actually happened on each error path (before the fix)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - if_id \u003e\u003d num_ifs (bad FLC from hardware)\n\n   Buffer state: still DMA-mapped (dma_map_page, FROM_DEVICE).\n\nWhat dpaa2_switch_free_fd() did: reads a garbage skb from the page,\ndereferences it in skb_tail_pointer() (wild pointer read), unmaps with\nthe wrong API, direction and length, then calls dev_kfree_skb() on the\ngarbage pointer (arbitrary free / memory corruption). The real page\nleaks.\n\n - build_skb() fails\n\n   Buffer state: already unmapped inside build_linear_skb().\n\nWhat dpaa2_switch_free_fd() did: a second unmap of the same address,\nplus the same garbage-pointer free. The page leaks.\n\n - __skb_vlan_pop() fails\n\nBuffer state: the skb was built over this page, so skb-\u003ehead \u003d\u003d\nfd_vaddr (the start of the buffer).\n\nWhat dpaa2_switch_free_fd() did: treats the first 8 bytes of the skb\u0027s\nown headroom as an sk_buff * and frees that. The real skb leaks; the\npage is unmapped a second time.\n\nAll three paths hand an attacker-influenced or stale pointer to\ndev_kfree_skb() inside NAPI (softirq) context. That is why this is a\nmemory-safety CVE and not just a leak.\n\n3. Tracing the code back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nRunning git log -L on all three functions, starting from\n74c1c9f5c0c30^, follows the rename out of staging:\n\n - dpaa2_switch_rx: changed only by 0b1b713704588 and b175dfd7e691b.\nThe second one (\"dpaa2-switch: mark skbs with offload_fwd_mark\") only\nadds the offload_fwd_mark line on the success path.\n\n - dpaa2_switch_build_linear_skb: changed only by 0b1b713704588.\n\n - dpaa2_switch_free_fd: changed only by 0b1b713704588.\n\nf48298d3fbfaa (\"staging: dpaa2-switch: move the driver out of staging\")\nmoved drivers/staging/fsl-dpaa2/ethsw/ethsw.c to\ndrivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c and left these\nfunctions unchanged. Nothing between 0b1b713704588 and the fix touched\nthe error path.\n\n4. How 0b1b713704588 introduced the defective code\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThis one commit added all three pieces at once:\n\n 1. dpaa2_switch_free_fd(), shaped for Tx from the start: skb\n    back-pointer, dma_unmap_single, DMA_TO_DEVICE, dev_kfree_skb. At\n    that point the driver had no Tx path. .ndo_start_xmit arrived in the\n    next commit, 7fd94d86b7f4a (\"staging: dpaa2-switch: add\n    .ndo_start_xmit() callback\"), which added\n    dpaa2_switch_build_single_fd() (the code that writes the\n    back-pointer), dpaa2_switch_tx_conf(), and the enqueue-failure\n    caller. So in 0b1b713704588 itself, the only caller of this Tx\n    helper was the Rx error label.\n\n 2. dpaa2_switch_build_linear_skb(), which hid the dma_unmap_page()\n    inside the skb builder. That made \"unmap, then free the page\"\n    impossible to do correctly from the caller, and meant a build_skb()\n    failure returned NULL with the page already unmapped.\n\n 3. dpaa2_switch_rx(), with its single err_free_fd: label calling\n    dpaa2_switch_free_fd() for every failure. That covers both the paths\n    before the skb is built (mapped page, no skb) and the path after\n    (skb wrapping the page).\n\nThe same commit also added the Rx buffer pool code\n(dpaa2_switch_add_bufs() with dev_alloc_pages / dma_map_page(...\nDMA_FROM_DEVICE), and dpaa2_switch_free_bufs()). So the correct Rx\nteardown sequence was already in the patch, just not used in\ndpaa2_switch_rx().\n\nFor comparison, the sibling driver this was modeled on, dpaa2-eth,\nalready had separate dpaa2_eth_free_rx_fd() and dpaa2_eth_free_tx_fd()\nbefore 0b1b713704588, and its Rx error path ends in free_pages((unsigned\nlong)vaddr, 0). dpaa2-switch got only the\nTx-style helper and used it on Rx.\n\n5. Code origin vs. vulnerable boundary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe series lands the control-interface (CTRL_IF) support in stages:\n\n    2877e4f7e189b  setup buffer pool and RX path rings\n    04abc97d3ef78  setup dpio\n    0b1b713704588  handle Rx path on control interface    \u003c- code origin\n    7fd94d86b7f4a  add .ndo_start_xmit() callback\n    613c0a5810b79  enable the control interface            \u003c- boundary\n\ndpaa2_switch_rx() has a single caller, dpaa2_switch_store_consume(),\nwhich calls it once for each frame descriptor dequeued from the control\ninterface Rx frame queue\n(DPSW_QUEUE_RX). That consumer runs from the NAPI poll routine, which\nis scheduled by the\nDPIO data-availability callback dpaa2_switch_fqdan_cb(). The MC\nfirmware only puts frames on the CTRL_IF queues once the control\ninterface of the DPSW object has been enabled with the\nDPSW_CMDID_CTRL_IF_ENABLE command.\n\nAt 0b1b713704588 (and at 7fd94d86b7f4a), dpaa2_switch_ctrl_if_setup()\nsets up the FQs, the buffer pool, the rings and the DPIO notifications,\nthen returns 0. Nothing sends CTRL_IF_ENABLE: the command ID\n(DPSW_CMDID_CTRL_IF_ENABLE, 0x0A2) and its wrapper\ndpsw_ctrl_if_enable() do not exist in the tree yet. The Rx queue\ntherefore never receives a frame, dpaa2_switch_rx() never runs, and none\nof the three error paths in section 2 can be reached. Its commit message\ndescribes the change accordingly: \"Enable the CTRL_IF of the switch\nobject, now that all the pieces are in place (buffer and queue\nmanagement, interrupts, NAPI instances etc)\".\n\n613c0a5810b79 adds DPSW_CMDID_CTRL_IF_ENABLE/DISABLE,\ndpsw_ctrl_if_enable()/dpsw_ctrl_if_disable(), and this to\ndpaa2_switch_ctrl_if_setup():\n\n    err \u003d dpsw_ctrl_if_enable(ethsw-\u003emc_io, 0, ethsw-\u003edpsw_handle);\n    if (err) {\n            dev_err(ethsw-\u003edev, \"dpsw_ctrl_if_enable err %d\\n\", err);\n            goto err_deregister_dpio;\n    }\n\nAt that commit, dpaa2_switch_probe() calls dpaa2_switch_init(). That\nfunction rejects a DPSW object created with DPSW_OPT_CTRL_IF_DIS\n(dpaa2_switch_supports_cpu_traffic() returns false -\u003e -EOPNOTSUPP), and\notherwise calls dpaa2_switch_ctrl_if_setup() unconditionally. So from\n613c0a5810b79 on, every bound switch has the control interface enabled.\nFrames are delivered to dpaa2_switch_rx(), and dpaa2_switch_rx(),\ndpaa2_switch_build_linear_skb() and the\nTx-shaped dpaa2_switch_free_fd() are byte-for-byte the code the fix\nreplaces. The only later change was b175dfd7e691b\u0027s offload_fwd_mark\nline on the success path.\n\nOne out-of-band route was considered and rejected. The fsl-mc userspace\ninterface (2cf1e703f066c, v5.12-rc1) only accepts commands on an\nallowlist. DPSW\u0027s CTRL_IF_ENABLE encodes as 0x0A21, which matches the\nDPDMUX_IF_GET_MAX_FRAME_LENGTH entry (value 0x0a20, mask 0xFFF0, no\nmodule-ID check). So a root process holding /dev/dprc.N could in theory\nopen the DPSW and enable CTRL_IF without the driver\u0027s involvement at\n0b1b713704588. That means sending raw firmware commands to an object a\nkernel driver owns, which is outside the kernel\u0027s threat model in the\nsame way /dev/mem is. It does not make 0b1b713704588 a reachable\nboundary.\n\nAll of these commits first ship in v5.13-rc1, so the affected range\ndoes not depend on the choice. dyad gives the same result for both\ncandidates:\n\n    --vulnerable 0b1b713704588: 5.13:0b1b7137...:7.2:74c1c9f5...\n    --vulnerable 613c0a5810b79: 5.13:613c0a58...:7.2:74c1c9f5...\n\n6. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Vulnerable boundary: 613c0a5810b79 (\"staging: dpaa2-switch: enable\n   the control interface\"), v5.13-rc1. This is the first commit where\n   the broken error path can be reached.\n\n - Code origin (root cause of the defect): 0b1b713704588 (\"staging:\n   dpaa2-switch: handle Rx path on control interface\"), v5.13-rc1.\n\n - Suggested tag:\n\n    Fixes: 0b1b71370458 (\"staging: dpaa2-switch: handle Rx path on control interface\")\n\n - Not the root cause:\n\n    - 7fd94d86b7f4a gave dpaa2_switch_free_fd() its intended Tx users;\n      it didn\u0027t touch Rx.\n\n    - b175dfd7e691b only changed the success path.\n\n    - f48298d3fbfaa was a pure file move.\n\n - Affected: every kernel from v5.13 up to the fix. The bug is\n   reachable on CONFIG_FSL_DPAA2_SWITCH systems (Layerscape / DPAA2\n   SoCs) whenever the control interface is enabled. Since 613c0a5810b79\n   that includes every switch the driver binds to, because probe rejects\n   DPSW objects that have the control interface disabled.\n\nThis is the third CVE from the same five-patch \"dpaa2-switch: various\nimprovements\" series, which an automated reviewer (sashiko) flagged.\nThe NAPI-on-remove fix, CVE-2026-97434, also traces to 0b1b713704588.\n\nLeftover issue the fix does not address\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nOne related bug in this function is still present upstream and wasn\u0027t\npart of this fix. In dpaa2_switch_rx() (currently around\ndpaa2-switch.c:3148-3152 on origin/master), the \"goto err_free_fd\" for a\nframe format other than single-buffer sits inside\n\"if (net_ratelimit())\":\n\n    if (dpaa2_fd_get_format(fd) !\u003d dpaa2_fd_single) {\n            if (net_ratelimit()) {\n                    netdev_err(netdev, \"Received invalid frame format\\n\");\n                    goto err_free_fd;\n            }\n    }\n\nWhen the rate limiter suppresses the message, that frame is passed to\ndpaa2_switch_build_linear_skb() instead of being dropped.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "30c9caca6dabed1c6db93e000e1605196255e9ca",
      "tree": "a3ab0b8985f9fbda6ec7636d9a5bcbfd769d9b7c",
      "parents": [
        "df9f5765c8120c495001a6d4eca0d35d1b925bdb"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:55:28 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98910: Add .vulnerable file\n\nVulnerable from: 0ab2d7532b05a (\"drm/amdkfd: prepare per-process debug\nenable and disable\"), by Jonathan Kim, first released in v6.5-rc1.\n\nThat commit created kfd_dbg_trap_enable(). Its first version already\nset up the debug session state and then returned -EFAULT without undoing\nany of it when copy_to_user() failed.\n\n218895820e6fc (\"drm/amdkfd: add per process hw trap enable and disable\nfunctions\"), also v6.5-rc1, made the bug worse by adding a partial\nunwind. It is not the origin.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix is 01112e241e37f. It has no Fixes: tag. It changes the\ncopy_to_user() failure branch of kfd_dbg_trap_enable()\n(drivers/gpu/drm/amd/amdkfd/kfd_debug.c:833):\n\n    if (copy_to_user(runtime_info, (void *)\u0026target-\u003eruntime_info, copy_size)) {\n            kfd_dbg_trap_deactivate(target, false, 0);  /* already there before the fix */\n    +       fput(target-\u003edbg_ev_file);\n    +       target-\u003edbg_ev_file \u003d NULL;\n    +       if (target-\u003edebugger_process)\n    +               atomic_dec(\u0026target-\u003edebugger_process-\u003edebugged_process_count);\n    +       target-\u003edebug_trap_enabled \u003d false;\n    +       kfd_unref_process(target);\n            r \u003d -EFAULT;\n    }\n\nEach added line undoes one step that kfd_dbg_trap_enable() performs\nbefore the copy_to_user() call:\n\n - Setup: f \u003d fget(fd); target-\u003edbg_ev_file \u003d f;\n   Undone by: fput() and setting target-\u003edbg_ev_file \u003d NULL.\n\n - Setup: kref_get(\u0026target-\u003eref) (the extra reference for the debug\n   session).\n   Undone by: kfd_unref_process(target).\n\n - Setup: target-\u003edebug_trap_enabled \u003d true.\n   Undone by: setting it back to false.\n\n - Setup: atomic_inc(\u0026debugger_process-\u003edebugged_process_count).\n   Undone by: atomic_dec(...).\n\n - Setup: kfd_dbg_trap_activate() (hardware).\n   Undone by: kfd_dbg_trap_deactivate(), which was already present.\n\nTogether these lines do the same work as kfd_dbg_trap_disable()\n(kfd_debug.c:683).\n\n2. Tracing the function\u0027s history\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L \u0027/^int\nkfd_dbg_trap_enable(/,/^}/:drivers/gpu/drm/amd/amdkfd/kfd_debug.c\u0027 up to\nthe fix returns only four commits:\n\n    01112e241e37f 2026-06-02  drm/amdkfd: Unwind debug trap enable on copy_to_user failure  \u003c- fix\n    7a1c5c6753858 2023-07-12  drm/amdkfd: enable cooperative groups for gfx11               \u003c- GWS pre-check only\n    218895820e6fc 2022-04-05  drm/amdkfd: add per process hw trap enable and disable ...    \u003c- adds activate + partial unwind\n    0ab2d7532b05a 2022-03-25  drm/amdkfd: prepare per-process debug enable and disable      \u003c- creates the function (ROOT)\n\nkfd_debug.c is a new file in 0ab2d7532b05a, so nothing older can be\ninvolved. No code was moved or refactored into this function from\nelsewhere.\n\n3. How 0ab2d7532b05a introduced the bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThis commit added the AMDKFD_IOC_DBG_TRAP ENABLE/DISABLE plumbing and\nthe new kfd_debug.c. Its version of the function:\n\n    int kfd_dbg_trap_enable(struct kfd_process *target, uint32_t fd,\n                            void __user *runtime_info, uint32_t *runtime_size)\n    {\n            ...\n            if (target-\u003edebug_trap_enabled)\n                    return -EALREADY;\n\n            copy_size \u003d min((size_t)(*runtime_size), sizeof(target-\u003eruntime_info));\n\n            f \u003d fget(fd);                                   /* (1) file reference */\n            ...\n            target-\u003edbg_ev_file \u003d f;\n\n            kref_get(\u0026target-\u003eref);                         /* (2) extra process ref */\n            target-\u003edebug_trap_enabled \u003d true;              /* (3) \"session active\" */\n\n            if (target-\u003edebugger_process)\n                    atomic_inc(\u0026target-\u003edebugger_process-\u003edebugged_process_count); /* (4) */\n\n            if (copy_to_user(runtime_info, (void *)\u0026target-\u003eruntime_info, copy_size))\n                    r \u003d -EFAULT;                            /* \u003c-- no unwind of (1)-(4) */\n\n            *runtime_size \u003d sizeof(target-\u003eruntime_info);\n            return r;\n    }\n\nThe same commit added kfd_dbg_trap_disable(), which is exactly the\nteardown of steps 1 to 4. So the author knew which state needed\nreleasing; it just wasn\u0027t released on the error path. The copy_to_user()\ncall comes after the session has been fully committed.\n\nThe caller treats -EFAULT as a failed enable\n--------------------------------------------\n\nThe same commit added this to kfd_ioctl_set_debug_trap():\n\n    case KFD_IOC_DBG_TRAP_ENABLE:\n            if (target !\u003d p)\n                    target-\u003edebugger_process \u003d p;\n            r \u003d kfd_dbg_trap_enable(target, args-\u003eenable.dbg_fd,\n                                    (void __user *)args-\u003eenable.rinfo_ptr,\n                                    \u0026args-\u003eenable.rinfo_size);\n            if (!r)\n                    target-\u003eexception_enable_mask \u003d args-\u003eenable.exception_mask;\n\nSo -EFAULT is a hard failure:\n\n - The exception mask is never installed.\n\n - Userspace is told the enable failed.\n\n - The kernel still considers the debug session open.\n\nHow it is triggered\n-------------------\n\nrinfo_ptr and rinfo_size come straight from the ioctl arguments. Any\nnon-zero rinfo_size combined with an unmapped or read-only rinfo_ptr\nmakes copy_to_user() fail.\n\nThe caller must be the target process itself or its ptrace parent (the\n\"ptrace_parent(target-\u003elead_thread) !\u003d current\" check).\n\nEffects in the 0ab2d7532b05a state\n----------------------------------\n\n - The debugger cannot retry. A second ENABLE returns -EALREADY because\n   debug_trap_enabled is still true. The debugger would have to send\n   DISABLE for an enable it was told had failed.\n\n - The target keeps the debugger\u0027s file. target-\u003edbg_ev_file still\n   holds the debugger\u0027s event fd. Event code (e.g. kfd_dbg_ev_raise(),\n   which checks \"debug_trap_enabled \u0026\u0026 dbg_ev_file\") keeps writing to\n   it.\n\n - Resources stay pinned. The extra kref on the target and the\n   debugger\u0027s debugged_process_count remain held. They are only released\n   by kfd_dbg_trap_disable(), which runs from\n   kfd_process_notifier_release_internal() when either process\u0027s mm is\n   torn down.\n\n - The target behaves as if it is being debugged. Later code reads\n   debug_trap_enabled in several places:\n\n    - GWS allocation is refused (kfd_chardev.c:1557).\n\n    - The CWSR debug workaround is applied (is_dbg_wa).\n\n    - runtime_disable(), added in 455227c4642c5, raises\n      EC_PROCESS_RUNTIME and blocks in\n      down_interruptible(\u0026p-\u003eruntime_enable_sema) waiting for an\n      acknowledgement from a debugger that thinks it never attached.\n\n4. Why 218895820e6fc is not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n218895820e6fc added the hardware side:\n\n - pre-checks for SOC15 and GWS support;\n\n - \"if (runtime_state \u003d\u003d DEBUG_RUNTIME_STATE_ENABLED)\n   kfd_dbg_trap_activate(target);\"\n\n - a single line in the failure branch:\n   \"kfd_dbg_trap_deactivate(target, false, 0);\"\n\nIt noticed that the error path needed unwinding but only unwound the\nhardware part it had just added. The pre-existing leak of steps 1 to 4\nfrom 0ab2d7532b05a was left untouched. That one line is the context line\nthe fix builds on. Everything the fix adds undoes state that\n0ab2d7532b05a set up.\n\nThe partial unwind also created the \"inconsistent trap state\" described\nin the fix\u0027s commit message. After the failure:\n\n - the hardware is deactivated;\n\n - debug_trap_enabled is still true;\n\n - runtime_state is still ENABLED.\n\nAny later kfd_dbg_trap_disable() (from the DISABLE ioctl or process\nteardown) or runtime_disable() therefore calls kfd_dbg_trap_deactivate()\na second time. In the code as it stood just before the fix, that second\ncall:\n\n - calls amdgpu_gfx_off_ctrl(adev, true) once more on devices without\n   RLC restore. This either trips\nWARN_ON_ONCE(gfx_off_req_count \u003d\u003d 0) or cancels another user\u0027s request\nto keep GFXOFF disabled.\n\n - calls release_debug_trap_vmid() again on devices without per-VMID\n   debug support. dqm-\u003etrap_debug_vmid is shared across the whole device\n   and the release does not check who owns it, so it can take the trap\n   VMID away from another process\u0027s debug session.\n\n7a1c5c6753858 only changed the GWS pre-check condition and does not\ntouch the error path.\n\n5. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 0ab2d7532b05a (\"drm/amdkfd: prepare per-process debug\n   enable and disable\"), v6.5-rc1.\n\n - Aggravating commit: 218895820e6fc, also v6.5-rc1. Since both landed\n   in v6.5-rc1, the affected range is the same either way: v6.5 up to\n   the fix.\n\n - Suggested tag:\n\n    Fixes: 0ab2d7532b05 (\"drm/amdkfd: prepare per-process debug enable and disable\")\n\nLeftover issues the fix does not address\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThese are separate from this CVE:\n\n 1. The failure path still always deactivates. It calls\n    kfd_dbg_trap_deactivate() even when activation was deferred\n    (runtime_state !\u003d ENABLED) or kfd_dbg_trap_activate() already\n    failed and unwound itself. kfd_dbg_trap_disable() guards this call\n    on runtime_state; the enable failure path does not. This came from\n    218895820e6fc.\n\n 2. target-\u003edebugger_process is never cleared on any ENABLE failure\n    path, now including -EFAULT. Once debugged_process_count is back\n    to 0, exit of the debugger no longer resets it.\n\n     - Possible consequence (not reproduced): the debugger exits, then\n       the target enables debugging on itself (target \u003d\u003d p, so the\n       pointer is not overwritten). The atomic_inc() would then run on\n       the freed debugger kfd_process.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "df9f5765c8120c495001a6d4eca0d35d1b925bdb",
      "tree": "0200105ed9aea10c06ab9ba3920ef784787cf421",
      "parents": [
        "9baadca8609ff6e524ee122296f70c66866219e0"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:49:59 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98909: Add .vulnerable file\n\nVulnerable from: ed3525eda4c49 (\"net: ethernet: ti: introduce cpsw\nswitchdev based driver part 1 - dual-emac\"), by Ilias Apalodimas,\nNov 2019, first released in v5.5-rc1.\n\nThat commit created drivers/net/ethernet/ti/cpsw_new.c and wrote the\nbroken error path. Nothing else introduced it and no later commit made\nit worse. The fix, b64f763b6074 (\"net: cpsw_new: unregister devlink on\nport registration failure\"), is in origin/master. It has no Fixes: tag,\nso the attribution comes from tracing the code history.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ncpsw_probe() in drivers/net/ethernet/ti/cpsw_new.c, before the fix:\n\n    skip_cpts:\n            ret \u003d cpsw_register_notifiers(cpsw);\n            if (ret)\n                    goto clean_cpts;\n\n            ret \u003d cpsw_register_devlink(cpsw);       /* step A: succeeds */\n            if (ret)\n                    goto clean_unregister_notifiers;\n\n            ret \u003d cpsw_register_ports(cpsw);         /* step B: fails */\n            if (ret)\n                    goto clean_unregister_notifiers; /* skips undoing A */\n            ...\n    clean_unregister_notifiers:\n            cpsw_unregister_notifiers(cpsw);\n    clean_cpts:\n            cpts_release(cpsw-\u003ecpts);\n            cpdma_ctlr_destroy(cpsw-\u003edma);\n    clean_dt_ret:\n            cpsw_remove_dt(cpsw);\n            ...\n\nIf cpsw_register_ports() fails after cpsw_register_devlink() succeeded,\nprobe jumps to the same label used when devlink itself failed, and\nnothing calls cpsw_unregister_devlink(). The fix adds a\nclean_unregister_devlink: label that runs cpsw_unregister_devlink(cpsw)\nand falls through to the notifier cleanup. cpsw_remove() already undoes\nall three steps, so only the probe failure path was missing it.\n\n2. Why the leak matters\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ncpsw_register_devlink() allocates a devlink instance with\ndevlink_alloc(..., dev), which takes a reference on the device through\nget_device() (net/devlink/core.c:458). It stores dl_priv-\u003ecpsw \u003d cpsw,\nregisters the switch_mode and ale_bypass params, and then calls\ndevlink_register(), which makes the instance visible to userspace.\n\nWhen cpsw_register_ports() fails (for example register_netdev() returns\nan error), probe returns an error and devres frees everything that was\ndevm-managed:\n\n - the cpsw_common structure (devm_kzalloc(), cpsw_new.c:1899);\n\n - the ALE structure and its regmap (devm_kzalloc() /\n   devm_regmap_init_mmio() in cpsw_ale_create());\n\n - the register mapping (devm_platform_get_and_ioremap_resource(),\n   cpsw_new.c:1929);\n\n - the port netdevs (devm_alloc_etherdev_mqs()).\n\nThe devlink instance is not devm-managed, so it outlives all of that.\nThe consequences:\n\n 1. Memory and reference leak. The devlink object, its params and the\n    device reference are never released.\n\n 2. Stale instance visible to userspace. \"devlink dev show\" still lists\n    platform/\u003caddr\u003e.switch after the driver failed to bind.\n\n 3. Use-after-free. The param callbacks dereference the freed cpsw:\n\n     - cpsw_dl_switch_mode_get() reads cpsw-\u003edata.dual_emac.\n\n     - cpsw_dl_switch_mode_set() and cpsw_dl_ale_ctrl_set() go through\n       the freed ALE/regmap to registers that are no longer mapped.\n\n    A \"devlink dev param show/set\" from userspace therefore touches\n    freed memory.\n\n 4. A re-probe or rebind registers a second devlink instance for the\n    same device while the stale one remains.\n\n3. Tracing the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ned3525eda4c49 (v5.5-rc1) is the root cause. It added cpsw_new.c as a\nbrand-new file (1673 lines). It did not move code from elsewhere:\nbefore it, no TI ethernet driver used devlink at all\n(\"git grep devlink ed3525eda4c49^ -- drivers/net/ethernet/ti/\" finds\nnothing). In the same commit:\n\n - cpsw_register_devlink() does devlink_alloc() + devlink_register() +\n   devlink_params_register() + devlink_params_publish().\n\n - cpsw_unregister_devlink() undoes those steps, but only cpsw_remove()\n   calls it.\n\n - cpsw_probe() has this ordering and these gotos:\n\n            ret \u003d cpsw_register_devlink(cpsw);\n            if (ret)\n                    goto clean_unregister_notifiers;\n\n            ret \u003d cpsw_register_ports(cpsw);\n            if (ret)\n                    goto clean_unregister_notifiers; /* devlink never\n                                                        unregistered */\n            ...\n    clean_unregister_notifiers:\n            cpsw_unregister_notifiers(cpsw);\n    clean_unregister_netdev:\n            cpsw_unregister_ports(cpsw);\n    clean_cpts:\n            ...\n\nThere is no devlink cleanup label anywhere in that error path. This is\nthe exact defect b64f763b6074 fixes.\n\"git log -S\u0027goto clean_unregister_notifiers\u0027\" returns only\ned3525eda4c49, which added both gotos, and b64f763b6074, which changed\none of them.\n\nOne side detail: in part 1, cpsw_unregister_notifiers() was called but\nnot yet defined anywhere. The helper only appeared in part 2, which\nlanded in the same v5.5-rc1 merge. That makes part 1 a non-building\nintermediate commit, but it doesn\u0027t change where the devlink leak came\nfrom.\n\nLater commits kept the bug:\n\n - 111cf1ab4da39 (\"net: ethernet: ti: introduce cpsw switchdev based\n   driver part 2 - switch\", v5.5-rc1): defined cpsw_register_notifiers()\n   / cpsw_unregister_notifiers() and added the notifier registration\n   step before devlink. It left both\n   \"goto clean_unregister_notifiers\" lines unchanged.\n\n - 84ea9c0a95d7b (\"net: ethernet: ti: cpsw: enable cpts irq\"): added\n   the CPTS misc IRQ and skip_cpts. Unrelated to the devlink cleanup.\n\n - 919d13a7e455c, db4278c55fa53, 0d98ff22de92e: devlink API refactors\n(device passed to devlink_alloc(), devlink_register() made void,\nregistration moved last). They changed only the internals of the two\ndevlink helpers, never the probe gotos.\n\n - 62db84b7efa63 (\"net: cpsw_new: Fix unnecessary netdev unregistration\n   in cpsw_probe() error path\", Feb 2026): removed the bogus\n   clean_unregister_netdev label (cpsw_unregister_ports() could run\n   twice or before registration). Its own Fixes: tag points at\n   ed3525eda4c4, the same origin, but it left the missing devlink\n   cleanup in place.\n\n - b64f763b6074: added clean_unregister_devlink and pointed the\n   cpsw_register_ports() failure at it. This is the fix.\n\nWhichever labels existed over time, the cpsw_register_ports() failure\npath never reached cpsw_unregister_devlink(), from the moment it was\nwritten in ed3525eda4c49 until b64f763b6074.\n\n4. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIntroduced by ed3525eda4c4 (\"net: ethernet: ti: introduce cpsw\nswitchdev based driver part 1 - dual-emac\"), first in v5.5-rc1. The\ncorresponding tag would be:\n\n    Fixes: ed3525eda4c4 (\"net: ethernet: ti: introduce cpsw switchdev based driver part 1 - dual-emac\")\n\nEvery stable tree that has cpsw_new.c (5.10 and newer) carries the bug\nuntil b64f763b6074 is backported.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "9baadca8609ff6e524ee122296f70c66866219e0",
      "tree": "a09f68124dc4dd0419899a615da4760e47a3ed38",
      "parents": [
        "43aee5a48db8f8071a2b0c700c05da013987ba5a"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:44:24 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98908: Add .vulnerable file\n\nVulnerable from: 6ebf182bfdf15 (\"sparx5: add support for configuring\nPSFP via tc\"), by Daniel Machon, merged for v6.3-rc1 (Feb 2023).\n\nThat commit created sparx5_tc_flower_psfp_setup() with no error\nunwinding at all, and the function body stayed the same until the fix\nlanded in v7.2-rc1. One helper bug the fix also corrects was written\nslightly earlier, in d2185e79ba8f1 from the same series, but nothing\ncould reach it until 6ebf182bfdf15 added a caller.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nsparx5_tc_flower_psfp_setup() reserves and programs three hardware\nresources, each tracked by a reference count in a pool, before it adds\ntwo actions to the VCAP rule:\n\n 1. Stream gate: sparx5_psfp_sg_add() takes a reference with\n    sparx5_pool_get_with_idx() and programs the ANA_AC_SG_* registers.\n\n 2. Flow meter (only if there is a police action): sparx5_psfp_fm_add()\n    takes a reference with sparx5_pool_get_with_idx() and programs the\n    policer.\n\n 3. Stream filter: sparx5_psfp_sf_add() always takes a fresh entry with\n    sparx5_pool_get() and writes ANA_AC_TSN_SF_CFG(sfid).\n\n 4. sparx5_isdx_conf_set() maps the ISDX to the filter and flow meter\n    in ANA_L2_TSN_CFG and ANA_L2_DLB_CFG.\n\n 5. vcap_rule_add_action_bit(VCAP_AF_ISDX_ADD_REPLACE_SEL) and\n    vcap_rule_add_action_u32(VCAP_AF_ISDX_VAL) attach the actions.\n\nBefore the fix, every failure from step 2 onward was a bare\n\"return ret;\", so whatever earlier steps had reserved was never\nreleased. The fix adds the unwind labels err_sf_del, err_fm_del and\nerr_sg_del. It also moves \"*id \u003d fm-\u003epol.idx\" in sparx5_psfp_fm_add()\nabove the \"already in use\" early return, so a reused flow meter reports\nits id back to the caller.\n\n2. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - git log -L on sparx5_tc_flower_psfp_setup() shows only two commits:\n\n    - 6ebf182bfdf15 (2023-02-02, v6.3-rc1) created the function. Its\n      body already had the bare \"return ret;\" after fm_add, sf_add and\n      both vcap_rule_add_action_*() calls, exactly as the fix found it.\n\n    - 3f9e46347a466 (2024-10, v6.13-rc1) only replaced the constant\n      SPX5_PSFP_SG_OPEN with n_gates - 1. It changed nothing about\n      error handling.\n\n - 6ebf182bfdf15 is also the first caller of sparx5_psfp_sg_add(),\n   sparx5_psfp_fm_add() and sparx5_psfp_sf_add(). Before it, git grep\n   finds them only as definitions and prototypes.\n\n - The commit wires this up in sparx5_tc_flower_replace(): the GATE and\n   POLICE action cases set tc_sg_idx and tc_pol_idx, and a non-zero\n   return from setup does \"goto out\". The out: label only calls\n   vcap_free_rule(vrule). That is generic VCAP code and knows nothing\n   about the PSFP pools. The same commit added\n   sparx5_tc_free_psfp_resources(), but only the destroy path calls it,\n   and only for a rule that already carries VCAP_AF_ISDX_VAL. A rule\n   that failed during setup never got that action, so nothing could\n   clean up after it.\n\n3. A failure that is reachable in practice\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nvcap_rule_add_action() returns -EINVAL when the field isn\u0027t part of the\nrule\u0027s actionset (vcap_actionfield_match_actionset()). In\nsparx5_vcap_ag_api.c, ISDX_ADD_REPLACE_SEL and ISDX_VAL exist only in\nthe IS0 actionsets, not in the IS2 or ES2 base types. 6ebf182bfdf15\nnever limited gate or police to IS0, and current master doesn\u0027t either;\nit only checks SPX5_FEATURE_PSFP.\n\nSo a flower filter in an IS2 chain with \"action gate ...\" (or \"action\npolice ...\") followed by \"action goto\" or \"action pass\" gets its\nactionset fixed to IS2 inside the action loop. Setup then:\n\n - takes a stream gate reference, a flow meter reference and a new\n   stream filter,\n\n - writes the ISDX mapping registers,\n\n - fails on ISDX_ADD_REPLACE_SEL with -EINVAL and returns without\n   releasing anything.\n\n-ENOMEM from the kzalloc() in vcap_rule_add_action(), or -ENOSPC from\nsf_add / fm_add, leak the same way in any chain.\n\n4. What the leak costs\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - The pools are static arrays in sparx5_psfp.c, so leaked entries stay\n   used until the module is reloaded.\n\n - Each failed attempt permanently uses up one stream filter (1024\n   entries) and, for each new gate or police index, one stream gate or\n   flow meter. Repeating the command (which needs CAP_NET_ADMIN)\n   eventually exhausts a pool, and every later PSFP offload fails with\n   -ENOSPC.\n\n - A leaked extra reference on a gate or meter that a real filter\n   shares means that when the real owner is deleted,\n   sparx5_psfp_sg_del() / sparx5_psfp_fm_del() see the count still above\n   zero. They return without resetting the hardware, so the old gate or\n   policer settings stay active.\n\n - The stream filter and ISDX mapping registers stay programmed.\n\n5. The flow meter id half (d2185e79ba8f1, a contributor)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nsparx5_psfp_fm_add() was written in d2185e79ba8f1 (\"net: microchip:\nsparx5: add support for PSFP flow-meters\", same series). It writes\n\"*id \u003d fm-\u003epol.idx\" only after the \"if (ret \u003e 1) return 0;\" early\nreturn. Unlike sparx5_psfp_sg_get(), which writes *id directly,\nsparx5_psfp_fm_get() writes into \u0026fm-\u003epol.idx. So when an existing\npolice index is reused, the pool reference goes up but the caller\u0027s\npsfp_fmid stays 0.\n\nThat is why the fix moves the assignment: otherwise its new err_fm_del\npath would try to delete flow meter 0 and leave the reused meter\u0027s extra\nreference behind. The function had no callers until\n6ebf182bfdf15, so this defect was unreachable before then and is not\nthe root cause.\n\nThe same bug also hurt the success path, which the fix\u0027s message\ndoesn\u0027t mention. A second filter sharing a police index got\nsparx5_isdx_conf_set(..., fmid \u003d 0), so its policer was never applied.\nIts destroy then read fmid 0 back from hardware and skipped\nsparx5_psfp_fm_del(), leaking the reference even on a normal delete.\n\n6. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe tag the fix should have carried (it has no Fixes: tag) is:\n\n    Fixes: 6ebf182bfdf1 (\"sparx5: add support for configuring PSFP via tc\")\n\nAny stable tree that has 6ebf182bfdf15 (v6.3 and later) has the bug.\n\nThe fix leaves two gaps from the same commit open:\n\n - Failures in sparx5_tc_flower_replace() after setup succeeds still\n   leak. These are sparx5_tc_select_protocol_keyset(), vcap_val_rule()\n   and vcap_add_rule(), which all go to out: / vcap_free_rule() only.\n\n - sparx5_psfp_fm_add() failing after its own pool get still leaks, for\n   example when sparx5_sdlb_group_get_by_rate() or\n   sparx5_sdlb_conf_set() fails.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "43aee5a48db8f8071a2b0c700c05da013987ba5a",
      "tree": "694878ac7b6a6ca59ba5174dffc44c5cea1ccfeb",
      "parents": [
        "02c7352d83d0ab584d45fddace8f62c960071cdf"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:39:09 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98907: Add .vulnerable file\n\nVulnerable from: 3c20917120ce6 (\"block/bdev: enable large folio support\nfor large logical block sizes\"), by Hannes Reinecke, first released in\nv6.15-rc1.\n\nThe very next patch in the same series, 47dd675323038 (\"block/bdev:\nlift block size restrictions to 64k\"), also in v6.15-rc1, is what made\nit reachable. The unlocked read in sb_write_pointer() dates from\n12659251ca5df (v5.11), but it was correct when written. The race only\nbecame harmful in v6.15.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n123b9a545f4d0 (\"btrfs: protect sb_write_pointer() with invalidate\nlock\") wraps the read_cache_page_gfp() call in sb_write_pointer()\n(fs/btrfs/zoned.c) with filemap_invalidate_lock()/unlock(). Filipe\u0027s\nlater 00608e34167fa (\"btrfs: use mapping shared locking for reading\nsuper block\") downgraded that to the shared variant. The commit message\nsays this is the same race with BLKBSZSET that 3f29d661e568 (\"btrfs:\nsync read disk super and set block size\", v6.19-rc7) fixed in\nbtrfs_read_disk_super().\n\nThat earlier fix missed one call. btrfs_read_disk_super() itself calls\nbtrfs_sb_log_location_bdev() before its own read, and on zoned devices\nthat call does a second, unlocked page-cache read:\n\n    btrfs_scan_one_device()       // bdev_file_open_by_path(path, BLK_OPEN_READ,\n                                  //   NULL, NULL): no holder\n      btrfs_read_disk_super(bdev, 0, false)\n        btrfs_sb_log_location_bdev()       // bdev_is_zoned()\n          sb_log_location()                // zones[0] is not conventional\n            sb_write_pointer()             // both SB log zones FULL\n              read_cache_page_gfp(mapping, ...)  x2, NO invalidate_lock\n                                                 \u003c-- this CVE\n        filemap_invalidate_lock(mapping)\n        read_cache_page_gfp(...)    // protected since 3f29d661e568\n\n2. The race\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe reader side (btrfs):\n\n - read_cache_page_gfp() ends up in do_read_cache_folio()\n(mm/filemap.c:4059), which never takes the invalidate lock. The generic\nread paths do take it shared: filemap_create_folio() at\nmm/filemap.c:2631, readahead at mm/readahead.c:333/497, and the fault\npaths.\n\n - On a cache miss, do_read_cache_folio() does three separate steps,\n   each reading the mapping\u0027s geometry at a different moment:\n\n    1. filemap_alloc_folio(gfp, mapping_min_folio_order(mapping))\n       reads the minimum folio order once.\n    2. filemap_add_folio() runs\n       VM_BUG_ON_FOLIO(folio_order(folio) \u003c\n                       mapping_min_folio_order(mapping))\n       (mm/filemap.c:859).\n    3. filemap_read_folio() goes through blkdev_read_folio() -\u003e\n       block_read_full_folio() -\u003e folio_create_buffers(), which reads\n       1 \u003c\u003c inode-\u003ei_blkbits again.\n\nThe writer side (block layer):\n\n - blkdev_bszset() needs only CAP_SYS_ADMIN. It opens the device\n   exclusively for a moment. That succeeds because the btrfs scan opened\n   the device without a holder.\n\n - It then calls set_blocksize(). That function takes inode_lock and\n   the invalidate lock exclusively, then runs sync_blockdev() and\n   kill_bdev(), sets i_blkbits \u003d 14, calls\n   mapping_set_folio_min_order(mapping, 2), and unlocks.\n\nHow it interleaves (4K pages, BLKBSZSET 16K):\n\n 1. The reader allocates an order-0 (4K) folio while the min order is\n    still 0.\n\n 2. The whole set_blocksize() section runs. kill_bdev() cannot\n    truncate the new folio because it is not in the page cache yet. The\n    min order becomes 2 and i_blkbits becomes 14.\n\n 3. The reader resumes with filemap_add_folio():\n\n     - With CONFIG_DEBUG_VM, the VM_BUG_ON_FOLIO fires.\n\n     - Without it, the 4K folio is inserted.\n       create_empty_buffers(folio, 16384) then calls\n       folio_alloc_buffers(), where\n       \"offset \u003d 4096; (offset -\u003d 16384) \u003e\u003d 0\" is false on the first\n       test. So head \u003d\u003d NULL, and \"bh-\u003eb_state |\u003d b_state\"\n       dereferences NULL (fs/buffer.c:1588-1591).\n\n     - That is the exact syzbot signature quoted in 3f29d661e568:\n       create_empty_buffers \u003c- block_read_full_folio \u003c-\n       do_read_cache_folio \u003c- read_cache_page_gfp.\n\nHolding the invalidate lock (shared is enough) across\nread_cache_page_gfp() covers the whole allocate -\u003e add -\u003e start-read\nwindow, so set_blocksize()\u0027s exclusive section can\u0027t land inside it.\n\n3. Why the root cause is 3c20917120ce6 and not 12659251ca5df\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n12659251ca5df (\"btrfs: implement log-structured superblock for ZONED\nmode\", Naohiro Aota, v5.11):\n\n - git blame -w -M -C shows the read_cache_page_gfp() lines unchanged\n   since this commit. The same commit also wired the zoned lookup into\n   btrfs_read_disk_super(), and therefore into device scan.\n\n - Later commits only touched nearby lines, and none changed locking:\n224941e8379a0 (bd_mapping), 02ca9e6fb5f66 (bytenr calculation),\nd2715d1db455e (loop variable), c51f0e6a1254b (generation accessor).\n\n - At that point the race was harmless:\n\n    - In v5.11 through v6.14, set_blocksize() /\n      blk_validate_block_size() rejected size \u003e PAGE_SIZE, so any\n      block size fit in an order-0 page.\n\n    - The buffer code reads the block size once per page and then uses\n      head-\u003eb_size throughout (since 45bce8f3e343, v3.7, confirmed in\n      v5.11\u0027s block_read_full_page()). A concurrent block-size change\n      therefore produced a consistent page, and kill_bdev() waited on\n      the locked page.\n\n    - The bdev mapping had no minimum folio order:\n      \"git grep mapping_set_folio_min_order\" in block/ is empty in\n      v5.11, v5.15, v6.1, v6.12 and v6.14.\n\n    - No rule yet required in-kernel readers of the bdev page cache to\n      hold the invalidate lock.\n\n3c20917120ce6 (v6.15-rc1, patch 6/8 of the \"bs \u003e ps for block devices\"\nseries):\n\n - It added mapping_set_folio_min_order(inode-\u003ei_mapping,\n   get_order(size)) to set_blocksize() and set_init_blocksize().\n\n - That made a runtime BLKBSZSET change the bdev mapping\u0027s folio\n   geometry underneath existing readers. That is the precondition for\n   both crashes above.\n\n - The CVE team already used this commit as the vulnerable commit for\n   the sibling CVE-2026-23181 (fix 3f29d661e568).\n\n47dd675323038 (v6.15-rc1, patch 7/8):\n\n - It raised the blk_validate_block_size() cap from PAGE_SIZE to\n   BLK_MAX_BLOCK_SIZE (64K).\n\n - With 3c20917120ce6 alone, get_order(size) is always 0, so\n   47dd675323038 is what actually makes a non-zero min order reachable.\n\n - Both commits shipped in v6.15-rc1, so the affected range is the same\n   whichever one is named. 3c20917120ce6 is chosen because it adds the\n   geometry change that crashes, and because that matches\n   CVE-2026-23181.\n\nc0e473a0d2264 (\"block: fix race between set_blocksize and read paths\",\nDarrick Wong, v6.15-rc4) is a fix, not the cause:\n\n - It made set_blocksize() hold inode_lock and the invalidate lock\n   exclusively, truncating the page cache before changing the geometry,\n   and protected the userspace read/write/fallocate paths.\n\n - It left kernel-internal read_cache_page_gfp() callers to take the\n   lock themselves. That is what 3f29d661e568 and this fix do for btrfs.\n\n4. Impact and scope\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Affected: kernels from v6.15 until the fix (6.18.53 / 7.2), built\n   with CONFIG_TRANSPARENT_HUGEPAGE\u003dy (4cdf1bdd45ac7 (\"block: reject bs\n   \u003e ps block devices when THP is disabled\") caps the block size at\n   PAGE_SIZE without THP) and with PAGE_SIZE below 64K.\n\n - Trigger: a zoned device whose primary superblock-log zones are both\n   full, scanned by btrfs (mount-time scan, BTRFS_IOC_SCAN_DEV,\n   devices-ready) while a CAP_SYS_ADMIN process issues BLKBSZSET with a\n   value larger than the page size.\n\n - Consequence: VM_BUG_ON, or a NULL-pointer dereference in\n   create_empty_buffers().\n\n - Mounted filesystems are not exposed: the btrfs_sb_log_location() /\n   btrfs_get_dev_zone_info() callers run with btrfs holding the device\n   exclusively, so BLKBSZSET\u0027s exclusive open fails with -EBUSY.\n\n - Older stable trees: the 5.15.222, 6.1.189, 6.6.158 and 6.12.111\n   backports are harmless hardening. Going by master history, those\n   trees cap the block size at PAGE_SIZE and have no bdev min-order.\n\nThe published dyad for this CVE currently says 0:0 (no vulnerable\ncommit). The equivalent tag would be:\n\n    Fixes: 3c20917120ce (\"block/bdev: enable large folio support for large logical block sizes\")\n\nThat gives a vulnerable range of 6.15 -\u003e 6.18.53 / 7.2.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "02c7352d83d0ab584d45fddace8f62c960071cdf",
      "tree": "1c6d10347afb65e6f7af657cb40a8ca87512ca2b",
      "parents": [
        "0e3a9a1b01bc471b64085c89bd5ed25f5853e578"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:29:37 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:16 2026 -0400"
      },
      "message": "CVE-2026-98906: Add .vulnerable file\n\nVulnerable from: 2ea1cbac267e2 (\"net: lan966x: Update FDMA to change\nMTU.\"), by Horatiu Vultur, first released in v5.19-rc1.\n\nThat commit created lan966x_fdma_reload(). The function changes RX\nstate before it tries the allocation, and its restore: path never puts\nthat state back. Two later commits added more state to the same\nunrestored path, and a third made the path safe enough to reach that the\nstale state became the remaining bug. None of those three introduced the\nflaw.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\naa97f11a76e54 (\"net: lan966x: restore RX state on reload failure\")\nfixes two gaps in the restore: path of lan966x_fdma_reload():\n\n 1. rx-\u003epage_order and rx-\u003emax_mtu are not restored. Both are set from\n    new_mtu before lan966x_fdma_rx_alloc() runs. On failure, the\n    restore path puts back rx-\u003epage_pool and rx-\u003efdma but not these\n    two fields. RX then restarts with the old pages, old ring and old\n    pool, but with the size values of the MTU change that failed.\n\n 2. The per-port XDP RXQ memory-model registration is not restored.\n    lan966x_fdma_rx_alloc_page_pool() unregisters the old pool from\n    every port\u0027s xdp_rxq and registers the new pool. This happens\n    before fdma_alloc_coherent() runs. If that call fails, the new\n    pool is destroyed, but each port\u0027s xdp_rxq still points at it.\n\n2. How 2ea1cbac267e2 introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe commit added this function (shortened):\n\n    static int lan966x_fdma_reload(struct lan966x *lan966x, int new_mtu)\n    {\n            /* Store these for later to free them */\n            rx_dma \u003d lan966x-\u003erx.dma;  tx_dma \u003d ...;  rx_dcbs \u003d lan966x-\u003erx.dcbs; ...\n            napi_synchronize(); napi_disable(); lan966x_fdma_stop_netdev();\n\n            lan966x_fdma_rx_disable(\u0026lan966x-\u003erx);\n            lan966x_fdma_rx_free_pages(\u0026lan966x-\u003erx);\n            lan966x-\u003erx.page_order \u003d round_up(new_mtu, PAGE_SIZE) / PAGE_SIZE - 1;   /* \u003c-- changed */\n            err \u003d lan966x_fdma_rx_alloc(\u0026lan966x-\u003erx);\n            if (err)\n                    goto restore;\n            ...\n    restore:\n            lan966x-\u003erx.dma \u003d rx_dma;            /* only dma/dcbs pointers come back,  */\n            lan966x-\u003etx.dma \u003d tx_dma;            /* page_order is never saved or       */\n            lan966x_fdma_rx_start(\u0026lan966x-\u003erx); /* restored                           */\n    restore_tx:\n            lan966x-\u003erx.dcbs \u003d rx_dcbs; ...\n            return err;\n    }\n\nThe design is \"change the state, try to allocate, partly roll back on\nfailure\". The ring pointers are saved, but page_order is overwritten\nbefore the allocation that can fail and is never saved. In that version\nrx-\u003epage_order controlled the shape of the whole RX ring:\n\n - build_skb(page_address(page), PAGE_SIZE \u003c\u003c rx-\u003epage_order) in\n   lan966x_fdma_rx_get_frame().\n\n - FDMA_DCB_INFO_DATAL(PAGE_SIZE \u003c\u003c rx-\u003epage_order) in\n   lan966x_fdma_rx_add_dcb(), which programs how many bytes the hardware\n   may write into each buffer when NAPI refills it.\n\n - dev_alloc_pages(rx-\u003epage_order), the dma_map_page() /\n   dma_unmap_single() sizes, and the __free_pages(..., rx-\u003epage_order)\n   order.\n\n - The \"nothing to do\" check in lan966x_fdma_change_mtu():\n\n       if (round_up(max_mtu, PAGE_SIZE)/PAGE_SIZE - 1 \u003d\u003d lan966x-\u003erx.page_order)\n               return 0;\n\nSo from the first version, a failed reload left the restarted ring with\na buffer size that didn\u0027t match its real buffers. It also left the MTU\ncheck believing the change had worked, so retrying the same\nMTU did nothing.\n\nBlame on the parent of the fix shows the page_order assignment (line\n835), the goto restore, and the restore: label all still come from\n2ea1cbac267e2. A history search (git log -G \u0027rx\\.page_order \u003d\u0027) shows no\ncommit before the fix ever added a restore of page_order.\n\n3. How it evolved (all on origin/master)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. 2ea1cbac267e2 \"Update FDMA to change MTU\" (v5.19-rc1): origin.\n\n     - page_order is changed before the alloc and never restored.\n\n 2. f0a65f815f640 \"Fix use of pointer after being freed\" (v5.19):\n\n     - Reordered the restore of dma/dcbs (smatch report). Still no\n       page_order.\n\n 3. 4a4b6848d1e93, 872ad758f9b7f (v6.1):\n\n     - Dropped TX from the reload and changed the max-frame\n       calculation. Restore path unchanged.\n\n 4. 6a2159be7604f \"Add basic XDP support\" (v6.2-rc1):\n\n     - Added the consumer named in the CVE:\n\n           xdp_init_buff(\u0026xdp, PAGE_SIZE \u003c\u003c lan966x-\u003erx.page_order,\n                         \u0026port-\u003exdp_rxq);\n\n 5. 11871aba19748 \"Use page_pool API\" (v6.2-rc1):\n\n     - Added lan966x-\u003erx.max_mtu \u003d new_mtu; next to page_order, also\n       unrestored.\n\n     - Added a restore of rx.page_pool only.\n\n     - The MTU check moved to max_mtu \u003d\u003d rx.max_mtu, so the stale\n       max_mtu now fools it.\n\n 6. 77ddda44411c3 \"Update rxq memory model\" (v6.2-rc1):\n\n     - Added the per-port xdp_rxq_info_unreg_mem_model() /\n       reg_mem_model(..., rx-\u003epage_pool) loop to\n       lan966x_fdma_rx_alloc_page_pool(), which runs inside the\n       reload\u0027s allocation with no rollback.\n\n     - This is the origin of the fix\u0027s second half.\n\n 7. 01a70754327bd, 89ba464fcf548 FDMA library refactor (v6.12-rc1):\n\n     - Ring buffer size moved into struct fdma (db_size), and restore\n       became memcpy(\u0026rx.fdma, \u0026fdma_rx_old, ...).\n\n     - page_order and max_mtu live outside struct fdma, so they stayed\n       stale.\n\n     - The XDP frame size became the main remaining user of\n       page_order.\n\n 8. 59c3d55a946ca \"fix use-after-free and leak in\n    lan966x_fdma_reload()\" (v7.0):\n\n     - Stopped freeing the old pages before the new allocation.\n\n     - The restore path now restarts with valid old pages, so the\n       stale size and pool registration became the remaining\n       inconsistency. This is exposure, not origin.\n\n 9. 076344a6ad9d1 \"fix page pool leak\" (v7.0):\n\n     - Added page_pool_destroy() when fdma_alloc_coherent() fails.\n\n     - This is the \"new pool is destroyed by lan966x_fdma_rx_alloc()\"\n       that the fix\u0027s comment refers to.\n\n4. What happens in today\u0027s code without the fix\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. A CAP_NET_ADMIN user raises a port\u0027s MTU, for example to 9000.\n    lan966x_port_change_mtu() programs DEV_MAC_MAXLEN_CFG, then\n    lan966x_fdma_change_mtu() calls __lan966x_fdma_reload(). Attaching\n    or detaching an XDP program also triggers a reload, through\n    lan966x_xdp_setup() and lan966x_fdma_reload_page_pool().\n\n 2. lan966x_fdma_reload() sets rx.page_order to 2 (about 9.6 KB frames\n    round up to 12 KB) and sets rx.max_mtu to the new frame size.\n\n 3. lan966x_fdma_rx_alloc() creates the new pool, and each port\u0027s\n    xdp_rxq drops the old pool and registers the new one. Then\n    fdma_alloc_coherent() fails under memory pressure. The new pool is\n    destroyed, but the XDP registrations keep it alive and empty.\n    page_pool_create() failing reaches the same restore path, with\n    stale page_order/max_mtu but no re-registration.\n\n 4. restore: puts back rx.page_pool and rx.fdma and restarts RX on the\n    old order-0 (4 KB) pages. page_order stays 2, max_mtu stays at the\n    jumbo value, and xdp_rxq still points at the dead new pool.\n\n 5. On the next received frame with XDP attached, lan966x_xdp_run()\n    reports a frame size of PAGE_SIZE \u003c\u003c 2 \u003d 16 KB for a 4 KB page.\n    The XDP core trusts that size:\n\n     - bpf_xdp_adjust_tail() lets a program grow the packet up to\n       about 12 KB past the real page, writing out of bounds.\n\n     - With XDP_REDIRECT to cpumap or veth, the 16 KB size is copied\n       into the redirected frame. build_skb_around() then places\n       skb_shared_info about 12 KB past the 4 KB page, corrupting\n       memory.\n\n 6. The lan966x_port_change_mtu() error path restores MAXLEN and\n    dev-\u003emtu, but rx.max_mtu still holds the failed target. Retrying\n    the same MTU hits max_mtu \u003d\u003d lan966x-\u003erx.max_mtu and returns 0\n    without reallocating. The port accepts jumbo frames while the RX\n    buffers are still the old ones.\n\n5. Why not the other candidates\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 59c3d55a946ca is what v1 of the fix named in its Fixes: tag\n(\u003c20260604112846.1420908-1-lgs201920130244@gmail.com\u003e, covering only\nthe XDP part). It only stopped freeing the old pages early; it didn\u0027t\ntouch the page_order/max_mtu lines or the XDP registration.\nBefore it, the same restore path restarted the ring on freed pages and\nalso had the stale page_order. After David Carlier\u0027s review widened the\npatch to cover page_order/max_mtu, v2 was sent without a Fixes: tag.\n\n - 6a2159be7604f only added a reader of the stale page_order.\n\n - 11871aba19748 and 77ddda44411c3 repeated the same incomplete\n   rollback for max_mtu and the XDP registration. If you want the origin\n   of each part, those are the answers for those two parts.\n\n6. Note on affected stable trees\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe CVE lists fixes only in 6.12.111, 6.18.53 and 7.2.\nstable/linux-6.1.y and stable/linux-6.6.y still have the original code:\npage_order set before allocation and never restored, plus max_mtu in\n6.6. They also still free the old pages before allocating\n(the problem fixed by 59c3d55a946ca). The fix can\u0027t apply there without\nthat commit, so those branches carry the root cause from 2ea1cbac267e2\nwith no fix listed.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "0e3a9a1b01bc471b64085c89bd5ed25f5853e578",
      "tree": "0a469cce51f1fb379b9289861148dcade89b038b",
      "parents": [
        "3dc7bdf138f1401ed6289677e0f9cd9256aa42a8"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:14:45 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98905: Add .vulnerable file\n\nVulnerable from: fb91065851cd5 (\"drm/amdkfd: Refactor queue wptr_bo\nGART mapping\"), by Philip Yang, 2024-06-20, first released in v6.12-rc1.\n\nThe fix 20a5e7ffdfec first shipped in v7.2-rc1. Its mainline original\nis 88ed96abbbe2.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix makes two changes in kfd_process_queue_manager.c:\n\n 1. The real fix. kfd_criu_restore_queue() now calls\n    kfd_queue_acquire_buffers(pdd, \u0026qp) before pqm_create_queue(). If\n    queue creation fails, it undoes this with kfd_queue_unref_bo_vas()\n    and kfd_queue_release_buffers().\n\n 2. Type cleanup. set_queue_properties_from_criu() now casts\n    read_ptr/write_ptr to (void __user *) instead of the stale\n    (uint32_t *).\n\nThe fix\u0027s commit message names the cause: when\nkfd_queue_acquire_buffers() was split off, the CRIU path \"was missed\",\nso the buffer fields in queue_properties stay NULL. The later partial\nrevert, 1cafa8b29e02 (\"Don\u0027t acquire buffers during CRIU queue\nrestore.\"), names the crash site:\n\n    The only reason acquire_buffers was necessary here was to avoid a\n    null ptr dereference in init_user_queue.\n\nIt replaces the acquire call with a !q_properties-\u003ewptr_bo check in\ninit_user_queue().\n\n2. Timeline\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. 626f7b3190b4c \"CRIU add queues support\" (v5.18-rc1): not the cause.\n\n     - It added set_queue_properties_from_criu() and\n       kfd_criu_restore_queue().\n\n     - At that time there was no write-pointer BO to acquire. The\n       (uint32_t *) casts matched the struct fields, which were\n       uint32_t * then.\n\n 2. e77a541f5dea0 \"Enable GFX11 usermode queue oversubscription\"\n    (v6.0-rc1): not the cause.\n\n     - MES firmware with API version \u003e\u003d 2 needs the queue\u0027s\n       write-pointer BO (wptr_bo) mapped into GART, the GPU\u0027s\n       system-memory address window.\n\n     - The lookup and GART mapping were written inline in\n       kfd_ioctl_create_queue(). The resulting wptr_bo was passed to\n       pqm_create_queue() as an explicit argument.\n\n     - The CRIU path passed NULL on purpose:\n\n           pqm_create_queue(..., \u0026queue_id, NULL, q_data, ...);\n\n     - Every consumer handled NULL:\n\n         - init_user_queue() just stored it:\n\n               (*q)-\u003ewptr_bo \u003d wptr_bo;\n\n         - add_queue_mes() used it only under:\n\n               if (q-\u003ewptr_bo) { ... wptr_mc_addr \u003d ... }\n\n         - The free path checked:\n\n               if (pqn-\u003eq-\u003ewptr_bo)\n                       amdgpu_amdkfd_free_gtt_mem(...);\n\n     - So a CRIU-restored queue simply had no wptr_mc_addr, and the\n       kernel did not crash.\n\n 3. fb91065851cd5 (v6.12-rc1): introduces the bug.\n\n     - It moved wptr_bo into struct queue_properties and removed the\n       parameter from pqm_create_queue()/init_user_queue().\n\n     - The field is filled only by the new helper\n       kfd_queue_acquire_buffers() in kfd_queue.c. That helper reserves\n       the VM root and does:\n\n           kfd_queue_buffer_get(vm, properties-\u003ewrite_ptr,\n                                \u0026properties-\u003ewptr_bo, PAGE_SIZE);\n\n     - Only kfd_ioctl_create_queue() was converted to call it. In\n       kfd_criu_restore_queue() the commit just deleted the NULL\n       argument. It added no acquire call, so qp.wptr_bo stays NULL\n       after memset(\u0026qp, 0, ...).\n\n     - In the same commit, the NULL-tolerant consumers became\n       unconditional:\n\n         - init_user_queue(), under enable_mes and MES API \u003e\u003d 2:\n\n               if (dev-\u003eadev !\u003d amdgpu_ttm_adev(q_properties-\u003ewptr_bo-\u003etbo.bdev))\n\n           and\n\n               amdgpu_amdkfd_map_gtt_bo_to_gart(q_properties-\u003ewptr_bo, ...);\n\n         - add_queue_mes(): the if (q-\u003ewptr_bo) guard was removed,\n           leaving:\n\n               queue_input.wptr_mc_addr \u003d\n                       amdgpu_bo_gpu_offset(q-\u003eproperties.wptr_bo) + wptr_addr_off;\n\n     - The commit also changed read_ptr/write_ptr in queue_properties\n       from uint32_t * to void __user *. It updated the casts in\n       set_queue_properties_from_user() but not in\n       set_queue_properties_from_criu(). That is exactly the second\n       \"missed at the same time\" item the fix cleans up.\n\n 4. Later commits in the same series (v6.12): extended the gap, did not\n    cause it.\n\n     - 68e599db7a549, 834368eab3692, b049504e211e8, 629568d25fea8 and\n       a1fc9f584c4aa extended kfd_queue_acquire_buffers(). It now also\n       takes the read-pointer, ring, EOP and context-save BOs, plus\n       per-mapping queue reference counts and SVM ranges.\n\n     - CRIU still skipped all of it. kfd_queue_unref_bo_vas(), which\n       the fix calls on its error path, comes from a1fc9f584c4aa.\n\n     - In the tree just before the fix, the only places that use these\n       fields without a NULL check are the two wptr_bo sites from\n       step 3.\n\n 5. 30837a49bd0ab \"Map wptr BO to GART unconditionally\" (v6.16-rc1):\n    widened, not originated.\n\n     - It removed the MES API \u003e\u003d 2 check, so the dereference now runs\n       on every device with enable_mes set.\n\n     - git blame on those lines lands here only because the code was\n       re-indented. The dereference itself was already present in\n       fb91065851cd5, and before this change it was reached on GFX11\n       hardware with MES API \u003e\u003d 2.\n\n3. How the crash happens (from fb91065851cd5 on)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. A process with CAP_CHECKPOINT_RESTORE or CAP_SYS_ADMIN issues\n    AMDKFD_IOC_CRIU_OP with KFD_CRIU_OP_RESTORE.\n\n 2. The call chain is criu_restore() -\u003e criu_restore_objects() -\u003e\n    kfd_criu_restore_queue().\n\n 3. kfd_criu_restore_queue() zeroes qp and calls\n    set_queue_properties_from_criu(), which never sets wptr_bo.\n\n 4. pqm_create_queue() handles a COMPUTE or SDMA queue by calling\n    init_user_queue().\n\n 5. With enable_mes set and MES API \u003e\u003d 2, init_user_queue() reads\n    q_properties-\u003ewptr_bo-\u003etbo.bdev -\u003e NULL pointer dereference and\n    kernel oops.\n\n 6. If that line were somehow passed, add_queue_mes() -\u003e\n    amdgpu_bo_gpu_offset(NULL) would fault next.\n\nEvery CRIU restore of a user queue on a MES-scheduled GPU hits this.\nNeither the contents nor the address of the restore data matter,\nbecause the acquire step never runs.\n\n4. Note for backporting\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n1cafa8b29e02 partially reverts this fix. In practice the acquire call\nfails on CRIU restore: the control-stack size from the CRIU plugin\ndoesn\u0027t match, and the SVM ranges may not be restored yet. Its\nreplacement for the acquire call is the NULL check in init_user_queue().\nAny tree that takes 20a5e7ffdfec should also take\n1cafa8b29e02. The trees that contain the bug are those with\nfb91065851cd5, i.e. v6.12 and later.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "3dc7bdf138f1401ed6289677e0f9cd9256aa42a8",
      "tree": "933da5ed2cf2392e9cf6715c06dbdd03e33bd234",
      "parents": [
        "675790dcebfc1fbc666acfbc764bc60d25cdc23d"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 15:01:43 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98904: Add .vulnerable file\n\nVulnerable from: 676f23eab75a (\"mailbox: imx: Support runtime PM\"), by\nAnson Huang, 2020-04-13, first released in v5.8-rc1.\n\nThe bug stayed in the driver until the fix 1f602619e408 (\"mailbox: imx:\nUse devm_pm_runtime_enable()\") landed in v7.2-rc1. The fix has no\nFixes: tag, and the CVE announcement doesn\u0027t name an introducing commit. The introducing commit comes from reading the fix and the lore thread that led to it, and from running git log -L over the tail of imx_mu_probe() and over imx_mu_remove().\n\n1. What the fix corrects\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix came out of a sashiko-bot review of v1 of Sebastian\u0027s\nthreaded-IRQ series (reply to PATCH 4/5, 2026-05-29), which flagged a\npre-existing High issue:\n\n    devm_mbox_controller_register() is called ... before\n    pm_runtime_enable() and before the probe function is guaranteed to\n    succeed. If a consumer requests a channel immediately after\n    registration and calls startup() ... If probe() subsequently fails\n    (e.g. pm_runtime_resume_and_get() fails), the error path calls\n    clk_disable_unprepare() ... devres cleanup ... unregisters the\n    mailbox controller and calls imx_mu_shutdown() ... register access\n    [with the clock off] -\u003e fatal bus error or system hang?\n\nSebastian agreed. Shutdown would run with runtime PM already disabled,\nand he proposed devm_pm_runtime_enable() plus moving the registration\nlater.\n\nThe pre-fix probe tail (1f602619e408^):\n\n    ret \u003d devm_mbox_controller_register(dev, \u0026priv-\u003embox);  // controller visible on mbox_cons\n    of_platform_populate(dev-\u003eof_node, NULL, NULL, dev);   // children may grab channels here\n    pm_runtime_enable(dev);                                 // RPM enabled only now\n    ret \u003d pm_runtime_resume_and_get(dev);  if (ret \u003c 0) goto disable_runtime_pm;\n    ret \u003d pm_runtime_put_sync(dev);        if (ret \u003c 0) goto disable_runtime_pm;\n    clk_disable_unprepare(priv-\u003eclk);\n    return 0;\n    disable_runtime_pm:\n            pm_runtime_disable(dev);          // manual teardown...\n    disable_clk:\n            clk_disable_unprepare(priv-\u003eclk); // ...clock gated...\n            return ret;                       // ...then devres unregisters the controller\n    ...\n    imx_mu_remove(): pm_runtime_disable(priv-\u003edev);  // runs BEFORE devres unregister\n\nThis produces three failure sequences.\n\n(a) Early use of the device during probe. Once\nmbox_controller_register()\n    adds the controller to mbox_cons, any consumer can bind a channel.\n    That includes a parallel or async probe, a deferred-probe retry, or\n    (since v6.9) a child probed synchronously by of_platform_populate().\n    The bind path is __mbox_bind_client() -\u003e imx_mu_startup() -\u003e\n    pm_runtime_get_sync(). With runtime PM still disabled, that call\n    returns -EACCES but still increments usage_count. imx_mu_startup()\n    ignores the return value, so it calls request_irq() and writes MU\n    control registers before the driver has brought up runtime PM.\n\n(b) Probe failure after the controller is published. If\n    pm_runtime_resume_and_get() or pm_runtime_put_sync() fails, probe\n    calls pm_runtime_disable() and clk_disable_unprepare() and returns\n    an error. Only then does devres_release_all() run\n    __devm_mbox_controller_unregister() -\u003e mbox_free_channel() -\u003e\n    imx_mu_shutdown(). At that point:\n\n     - imx_mu_xcr_rmw() / imx_mu_xcr_clr_shut() touch MU registers with\n       the clock gated, which on i.MX is an external abort or hang.\n\n     - pm_runtime_put_sync() hits a disabled device: rpm_idle() returns\n       -EAGAIN and the runtime-suspend callback never runs.\n\n     - Afterwards the devm-allocated priv, which holds mbox_chans[], is\n       freed while the consumer may still hold its struct mbox_chan *.\n\n(c) Unbind ordering. .remove() calls pm_runtime_disable() before devres\n    unregisters the controller. Every imx_mu_shutdown() -\u003e\n    pm_runtime_put_sync() during unbind therefore can\u0027t suspend, so the\n    MU clock and power domain enable is leaked.\n\nThe fix enables runtime PM with devm_pm_runtime_enable() before\nanything is published. It registers the controller only after the\nresume/put check has succeeded, and it drops every manual\npm_runtime_disable(). Devres LIFO order then becomes \"unregister\ncontroller (imx_mu_shutdown() with runtime PM live), then disable\nruntime PM\". (The fix\u0027s commit message says \"delaying the\npm_runtime_enable() into the removal path\"; it means the *disable*.)\n\n2. Tracing back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 2bb7005696e2 (initial driver), v4.19: mbox_controller_register() is\n   the last statement of probe. remove() unregisters first, then gates\n   the clock. Correct order, no runtime PM.\n\n - 4013286c7231 (devm registration), v5.0:\n\"return devm_mbox_controller_register(...)\" is still the last\nstatement, so nothing can fail after publication and there is no runtime\nPM. Unregistration now runs after .remove(). That creates a separate\nclock-ordering problem in remove() (clk_disable_unprepare() before\nshutdown), but not this bug.\n\n - 676f23eab75a (Support runtime PM), v5.8-rc1: introduces the bug\n   (section 3).\n\n - 47303f943895, v5.8-rc1: adds clock disable on registration failure.\n   Doesn\u0027t change the order.\n\n - bb2b2624dbe2, v5.8-rc1: adds runtime-PM clock callbacks and\n   clk_disable_unprepare() in the disable_runtime_pm path. This makes\n   the \"clock already off when shutdown runs\" crash concrete; it\n   aggravates the bug rather than causing it.\n\n - 504ff5b00853, v5.19: get_sync + put_noidle becomes\n   pm_runtime_resume_and_get(). No functional change.\n\n - f0e0110c189e, v6.9: adds the disable_clk label. Cosmetic for this\n   bug.\n\n - 2a0ac450128b (populate sub-nodes), v6.9-rc1: inserts\n   of_platform_populate() into the window, so children probing\n   synchronously can grab channels before pm_runtime_enable(). This\n   widens the window but doesn\u0027t create it.\n\n - a0c313d08d15, v6.8: remove() now returns void. Trivial.\n\nBefore 676f23eab75a the driver contained zero pm_runtime references,\nand probe ended with:\n\n    return devm_mbox_controller_register(dev, \u0026priv-\u003embox);\n\n3. How 676f23eab75a introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n     static int imx_mu_startup(struct mbox_chan *chan)\n    +\tpm_runtime_get_sync(priv-\u003edev);            // return value ignored\n     static void imx_mu_shutdown(struct mbox_chan *chan)\n    +\t\tpm_runtime_put_sync(priv-\u003edev);\n    +\tpm_runtime_put_sync(priv-\u003edev);\n     ...\n    -\treturn devm_mbox_controller_register(dev, \u0026priv-\u003embox);\n    +\tret \u003d devm_mbox_controller_register(dev, \u0026priv-\u003embox);\n    +\tif (ret)\n    +\t\treturn ret;\n    +\n    +\tpm_runtime_enable(dev);                    // AFTER publication\n    +\tret \u003d pm_runtime_get_sync(dev);\n    +\tif (ret \u003c 0) { pm_runtime_put_noidle(dev); goto disable_runtime_pm; }\n    +\tret \u003d pm_runtime_put_sync(dev);\n    +\tif (ret \u003c 0) goto disable_runtime_pm;\n    +\treturn 0;\n    +disable_runtime_pm:\n    +\tpm_runtime_disable(dev);                   // manual, runs before devres unregister\n    +\treturn ret;\n     static int imx_mu_remove(...)\n     \tclk_disable_unprepare(priv-\u003eclk);\n    +\tpm_runtime_disable(priv-\u003edev);             // runs before devres unregister\n\nThis one commit did three things:\n\n 1. It made channel operations depend on runtime PM. startup() and\n    shutdown() now rely on pm_runtime_get_sync()/pm_runtime_put_sync()\n    to power the MU. That was the commit\u0027s whole purpose: gate VPU MUs\n    via their power domain.\n\n 2. It enabled runtime PM after the controller was already public.\n    That opened the window in (a), where imx_mu_startup() runs against\n    a device whose runtime PM is still disabled.\n\n 3. It added fallible steps after devm_mbox_controller_register() and\n    tore runtime PM down by hand. Both the error path and remove()\n    disable runtime PM before the devm-managed\n    mbox_controller_unregister() -\u003e imx_mu_shutdown() runs. That is the\n    inverted teardown in (b) and (c).\n\nThe fix exactly undoes these lines: pm_runtime_enable() moves before\nregistration as devm_pm_runtime_enable(), the disable_runtime_pm label\ngoes, and the pm_runtime_disable() in remove() goes. Every structural\nelement the fix corrects first appears in 676f23eab75a. bb2b2624dbe2\n(the clock disable in the error path) and 2a0ac450128b\n(of_platform_populate() inside the window) only made the existing flaw\neasier to hit.\n\n4. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Introduced: 676f23eab75a, v5.8-rc1.\n\n - Fixed: 1f602619e408, v7.2-rc1.\n\n - Stable backports named in the CVE mail: 6.12.111 (b30a047744ea) and\n   6.18.53 (adcaa21d50c3).\n\n - Not covered: 5.10, 5.15, 6.1 and 6.6 contain the introducing commit,\n   and the CVE mail lists no fix for them.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "675790dcebfc1fbc666acfbc764bc60d25cdc23d",
      "tree": "6cfa3d13441c180ca0dcb11bc7911f83ffa2745b",
      "parents": [
        "e5567ac27ab41fb839ec9bec7f3bb61338a9946a"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 14:52:59 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98903: Add .vulnerable file\n\nVulnerable from: 0a67003b1985c (\"mailbox: imx: add SCU MU support\"), by\nPeng Fan, 2020-03-19, first released in v5.7-rc1.\n\nThe bug stayed in the driver until the fix 5ccea7eacb778 (\"mailbox:\nimx: Add a channel shutdown field\") landed in v7.2-rc1. The fix has no\nFixes: tag, so the introducing commit comes from tracing the code history.\n\n1. What the fix addresses\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix touches only one place where an interrupt gets switched back\non. At the end of imx_mu_specific_rx(), it changes\n\n    imx_mu_xcr_rmw(priv, IMX_MU_RCR, IMX_MU_xCR_RIEn(priv-\u003edcfg-\u003etype, 0), 0);\n\nto imx_mu_xcr_set_act(). That helper sets RIEn only if cp-\u003eshutdown is\nstill false, and it checks the flag under priv-\u003excr_lock.\nimx_mu_shutdown() now uses imx_mu_xcr_clr_shut(), which sets the flag\nand clears the enable bit inside that same critical section. The TX and\nRXDB changes in shutdown() only set the flag. Nothing reads it except\nset_act(), and only specific_rx calls set_act(). So the bug being fixed\nis purely the RX ISR re-arming the RX interrupt (RIEn) after shutdown\nhas cleared it.\n\n2. The race\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n    CPU0 (hard IRQ)                  CPU1 (mbox_free_channel -\u003e -\u003eshutdown)\n    imx_mu_isr()\n      imx_mu_specific_rx()\n        xcr_rmw(RCR, clr RIEn(0))\n        read RR0, then readl_poll_timeout()\n          for the remaining words ...\n                                     imx_mu_shutdown()\n                                       xcr_rmw(RCR, clr RIEn(cp-\u003eidx\u003d\u003d0))\n        xcr_rmw(RCR, set RIEn(0))  \u003c-- re-arms the bit shutdown just cleared\n        mbox_chan_received_data()\n                                       free_irq() -\u003e waits for CPU0\u0027s handler\n                                       (returns with RIEn(0) still set and\n                                        the RX channel\u0027s handler gone)\n\nxcr_lock makes each read-modify-write atomic, but the \"is this channel\nstill alive?\" decision is never made under that lock. Shutdown\u0027s clear\nand the ISR\u0027s set are two separate atomic operations, and they can\nhappen in either order. free_irq() doesn\u0027t help: it waits for the\nrunning handler to finish, which is exactly when the handler re-arms the\nbit.\n\nWhat goes wrong afterwards: with the SCU configuration, every channel\ncalls request_irq(priv-\u003eirq, imx_mu_isr, IRQF_SHARED | IRQF_NO_SUSPEND,\n...) on the same line (TX0, RX0, RXDB0-3). After RX0 is freed, the next\nmessage in RR0 sets RFn(0). With RIEn(0) still on, that keeps the line\nasserted until RR0 is read. The handlers still attached (TX, RXDB) mask\nthe status with their own bits and return IRQ_NONE. The IRQ core then\nreports \"irq N: nobody cared\" and disables the line, which also kills\nthe remaining channels on that MU, including SCU doorbell and IPC\ntraffic.\n\n3. How 0a67003b1985c introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThat commit added SCU-style multi-word messaging. A message arrives in\nRR0 first and the rest is polled from RR1..3. To stop RR0\u0027s interrupt\nfrom firing while the handler polls, it added imx_mu_scu_rx(). This was\nthe first ISR-side function to clear and then re-set an interrupt enable\nbit:\n\n    static int imx_mu_scu_rx(struct imx_mu_priv *priv, struct imx_mu_con_priv *cp)\n    {\n            ...\n            imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_RIEn(0));        /* disable on entry */\n            *data++ \u003d imx_mu_read(priv, priv-\u003edcfg-\u003exRR[0]);\n            ...\n            for (i \u003d 1; i \u003c msg.hdr.size; i++) {\n                    ret \u003d readl_poll_timeout(priv-\u003ebase + priv-\u003edcfg-\u003exSR, xsr,\n                                             xsr \u0026 IMX_MU_xSR_RFn(i % 4), 0, 100);   /* busy-wait in hard IRQ */\n                    ...\n            }\n            imx_mu_xcr_rmw(priv, IMX_MU_xCR_RIEn(0), 0);         /* unconditional re-enable */\n            mbox_chan_received_data(cp-\u003echan, (void *)\u0026msg);\n\nIt is wired up as .rx \u003d imx_mu_scu_rx in imx_mu_cfg_imx8_scu and called\nfrom imx_mu_isr() in hard-IRQ context. The same commit\u0027s\nimx_mu_init_scu() gives the RX channel cp-\u003eidx \u003d 0, so the RIEn(0) the\nISR re-sets is exactly the RIEn(cp-\u003eidx) that imx_mu_shutdown() clears\njust before free_irq():\n\n    case IMX_MU_TYPE_RX:\n            imx_mu_xcr_rmw(priv, 0, IMX_MU_xCR_RIEn(cp-\u003eidx));\n            break;\n    ...\n    free_irq(priv-\u003eirq, chan);\n\nNothing tells the ISR the channel is going away, so the re-enable at\nthe end is unconditional. That missing check is what cp-\u003eshutdown adds\n6 years later.\n\n4. Why it isn\u0027t older\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIn 0a67003b1985c^ (the original driver, 2bb7005696e22, plus its\nshutdown fixes 11108f4671bda, bf159d151a0b8 and 5f0af07e89199), the ISR\nonly ever clears enable bits. The TX-done path clears TIEn, generic RX\njust reads RRn, and RXDB just acks GIPn. RIEn/GIEn were only ever set in\nstartup(). Racing a clear against a clear is harmless, so the race needs\na set inside the ISR, and 0a67003b1985c added the first one.\n\n5. Later commits, none of which introduced the bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 9d8ca628c0286: changed the header size check. The clear/re-set pair\n   is unchanged.\n\n - 32f7443d41392, f689a7cf75975 (added IMX_MU_RCR), 4f0b776ef5831\n(added the type argument to RIEn): mechanical rewrites of the same two\ncalls.\n\n - 97961f78e8bc7 (v5.16): renamed imx_mu_scu_rx to imx_mu_specific_rx\n   and reused it for i.MX8ULP S4/ELE. Same logic, now on more SoCs.\n\n - 81a9d3b9301c9 (v5.18): raised the in-ISR poll timeout from 100 µs to\n   5 * USEC_PER_SEC. The handler can now spin for seconds between the\n   clear and the re-set, which makes the race much easier to hit, but\n   the race already existed.\n\n - 81f91d6aeb478: made the RR count dynamic (num_rr). The pattern is\n   unchanged.\n\nThe later commits fbc0f319cee18 and the threaded-handler split\n(692e1bc96a5dc, e55bea377d492, 3225a745f5174, 36cac4b5101f8) all come\nafter the fix and are part of the same Siewior series.\n\n6. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nv5.7-rc1 up to the fix 5ccea7eacb778 in v7.2-rc1. It affects every\nconfig using the specific_rx path: fsl,imx8-mu-scu, and since v5.16 also\nthe i.MX8ULP S4 and later ELE variants.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e5567ac27ab41fb839ec9bec7f3bb61338a9946a",
      "tree": "65bede1fad8a461aa4eb2d092dcde1e9ee632532",
      "parents": [
        "7bb2ef8e22d868e13e2aa1701aed72346c07d8eb"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 14:47:50 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98902: Add .vulnerable file\n\nVulnerable from: 2b6b24574256c (\"md/raid5: ensure whole batch is\ndelayed for all required bitmap updates.\"), by NeilBrown, 2015-05-21,\nfirst released in v4.1-rc6.\n\nThe md/raid5 stripe-batch bm_seq comparison isn\u0027t wrap-safe. That\ncommit added the plain \"sh-\u003ebatch_head-\u003ebm_seq \u003e seq\" comparison that\nthe fix\n00e93faf4cea9 (\"md/raid5: let stripe batch bm_seq comparison\nwrap-safe\", v7.2-rc1) rewrites.\n\ngit blame on the fix\u0027s parent shows every line of the block still comes\nfrom 2b6b24574256c. git log -S\"batch_head-\u003ebm_seq \u003e seq\" finds only two\ncommits: that one and the fix. The code was never moved or rewritten in\nbetween. The function now sits at line 996 instead of 837, but the text\nis unchanged. Every kernel from v4.1 until the fix (00e93faf4cea9,\nv7.2-rc1) is affected.\n\nSuggested tag:\n\n  Fixes: 2b6b24574256 (\"md/raid5: ensure whole batch is delayed for all required bitmap updates.\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n     if (test_and_clear_bit(STRIPE_BIT_DELAY, \u0026sh-\u003estate)) {\n             int seq \u003d sh-\u003ebm_seq;\n             if (test_bit(STRIPE_BIT_DELAY, \u0026sh-\u003ebatch_head-\u003estate) \u0026\u0026\n    -            sh-\u003ebatch_head-\u003ebm_seq \u003e seq)\n    +            sh-\u003ebatch_head-\u003ebm_seq - seq \u003e 0)\n                     seq \u003d sh-\u003ebatch_head-\u003ebm_seq;\n             set_bit(STRIPE_BIT_DELAY, \u0026sh-\u003ebatch_head-\u003estate);\n             sh-\u003ebatch_head-\u003ebm_seq \u003d seq;\n     }\n\nIn stripe_add_to_batch_list(), when a stripe sh joins a batch, its\nbitmap delay is moved onto the batch head. The head should keep\nwhichever of the two sequence numbers is newer. The old code chose with\na raw signed \"\u003e\". The fix uses the subtraction form, which stays correct\nwhen the counter wraps.\n\n2. How the bitmap sequence numbers work\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThis scheme dates from NeilBrown\u0027s 72626685dc66d (2005, \"md: add\nwrite-intent-bitmap support to raid5\"). It was reworked in ae3c20ccf84c8\n(2006, \"fix some small races in bitmap plugging in raid5\"), and the\nheader comment of raid5.c describes it:\n\n - conf-\u003eseq_flush and conf-\u003eseq_write are int (raid5.h:636).\nsh-\u003ebm_seq is also int (raid5.h:214). They start at 0 because conf is\nzero-allocated.\n\n - On the first write to a stripe, __add_stripe_bio() sets sh-\u003ebm_seq \u003d\n   conf-\u003eseq_flush + 1 and sets STRIPE_BIT_DELAY. This means: the\n   stripe\u0027s bitmap bit was dirtied in memory after batch seq_flush was\n   closed, so it will only be on disk once batch seq_flush+1 has been\n   written.\n\n - do_release_stripe() puts a delayed stripe on conf-\u003ebitmap_list while\n   sh-\u003ebm_seq - conf-\u003eseq_write \u003e 0.\n\n - On each pass with a non-empty bitmap_list, raid5d() does the\n   following:\n\n    1. seq_flush++, which closes the current batch.\n    2. Drops device_lock and calls bitmap_ops-\u003eunplug() to write the\n       dirty bitmap pages.\n    3. Sets seq_write \u003d seq_flush.\n    4. Calls activate_bit_delay() to re-release the waiting stripes.\n\nThese counters are meant to wrap. Since 2005/2006, every ordering test\nbetween them has used the subtraction form:\n\n - The original code had conf-\u003eseq_flush - conf-\u003eseq_write \u003e 0.\n\n - ae3c20ccf84c8 added sh-\u003ebm_seq - conf-\u003eseq_write \u003e 0, which is still\n   at raid5.c:259.\n\nThe kernel builds with -fno-strict-overflow (Makefile:1173), so signed\noverflow wraps and \"a - b \u003e 0\" orders correctly across the wrap.\n\nBefore 2b6b24574256c, the only bm_seq comparison in raid5.c was the\nwrap-safe one (checked against 2b6b24574256c^).\n\n3. How 2b6b24574256c introduced the bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nShaohua Li\u0027s 59fc630b8b5f9f (\"RAID5: batch adjacent full stripe write\",\nv4.1-rc1) added stripe batching. Adjacent full-stripe writes are chained\nbehind a batch head and run as one unit:\n\n - handle_stripe() returns early for batch members\n   (clear_batch_ready(sh) returns 1).\n\n - Only the head is handled, and it writes the whole batch.\n\n - The head\u0027s own STRIPE_BIT_DELAY/bm_seq is the only gate.\n   do_release_stripe() and handle_stripe_dirtying()\n(!test_bit(STRIPE_BIT_DELAY, \u0026sh-\u003estate) before\nschedule_reconstruction) check only the head.\n\n59fc630b8b5f9f didn\u0027t carry a member\u0027s bitmap requirement over to the\nhead at all. That was a separate bug, and 2b6b24574256c fixed it a few\nweeks later:\n\n\"When we add a stripe to a batch, we need to be sure that head stripe\nwill wait for the bitmap update required for the new stripe.\"\n\nIt did this by clearing the member\u0027s STRIPE_BIT_DELAY and giving the\nhead the larger of the two bm_seq values. That is the first place\nraid5.c compares two sequence numbers for ordering, and it used\n\"sh-\u003ebatch_head-\u003ebm_seq \u003e seq\" instead of the existing subtraction\nidiom. Because the member\u0027s own flag is cleared, the head\u0027s value is the\nmember\u0027s only protection. Choosing the wrong value removes the member\u0027s\nwait completely.\n\nThe next commit, d0852df543e5a (\"close race between STRIPE_BIT_DELAY\nand batching\"), added STRIPE_BITMAP_PENDING and the !sh-\u003ebatch_head\nguard in add_stripe_bio. It didn\u0027t touch the comparison. Later changes\nonly moved the function or worked around it (for example cd5fc65338181\nmoved bitmap_startwrite to the md layer). The bad line was never\nchanged.\n\n4. Failure sequence\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nA signed \"\u003e\" only goes wrong at the INT_MAX -\u003e INT_MIN step. That\nhappens after 2^31 batch closes from array start, then every 2^32 after\nthat. seq_flush goes up once per raid5d pass with stripes waiting on the\nbitmap. Under sustained write load with a bitmap, that can happen\nthousands of times a second, so a long-running array can reach the wrap\nin days to weeks.\n\nStart with seq_flush \u003d\u003d seq_write \u003d\u003d INT_MAX-1:\n\n 1. A full-stripe write reaches stripe H. __add_stripe_bio sets\n    H-\u003ebm_seq \u003d INT_MAX and STRIPE_BIT_DELAY. H is still\n    STRIPE_BATCH_READY. On release, INT_MAX - (INT_MAX-1) \u003d 1 \u003e 0, so H\n    goes on bitmap_list.\n\n 2. raid5d closes the batch: seq_flush \u003d INT_MAX. It drops device_lock\n    and starts writing the bitmap pages.\n\n 3. While that write is in flight, a full-stripe write reaches the next\n    stripe S in the same chunk. Its bitmap bit is dirtied in memory,\n    possibly too late for the write already in flight.\n    S-\u003ebm_seq \u003d INT_MAX + 1, which wraps to INT_MIN. Logically S needs\n    batch INT_MAX+1.\n\n 4. stripe_add_to_batch_list(S, H): S\u0027s STRIPE_BIT_DELAY is cleared and\n    seq \u003d INT_MIN. H has STRIPE_BIT_DELAY and INT_MAX \u003e INT_MIN is true,\n    so seq \u003d INT_MAX. H keeps the older batch number and S\u0027s requirement\n    is lost. The fixed check gives INT_MAX - INT_MIN \u003d -1, which is not\n    \u003e 0, so the newer INT_MIN is kept.\n\n 5. raid5d sets seq_write \u003d INT_MAX and calls activate_bit_delay(). For\n    H, INT_MAX - INT_MAX \u003d 0, which is not \u003e 0, so STRIPE_BIT_DELAY is\n    cleared and H goes to handle_list. handle_stripe(H) writes data and\n    parity for the whole batch, including S.\n\n 6. S\u0027s bitmap bit only reaches disk with the next batch. If the machine\n    crashes or loses power between steps 5 and 6, the on-disk bitmap\n    says S\u0027s region is clean. After the unclean shutdown, the\n    bitmap-driven resync skips it, while data and parity may be\n    half-written across the member disks.\n\nThe result is undetected parity inconsistency, the RAID5 \"write hole\"\nthe write-intent bitmap is supposed to prevent. If a member disk later\nfails, rebuilding that stripe returns corrupted data.\n\nThe opposite order fails the same way. If the head holds the wrapped\nINT_MIN (newer) and the joining stripe holds INT_MAX (older), then\nINT_MIN \u003e INT_MAX is false. The head\u0027s own bm_seq drops to INT_MAX, so\nthe head and its batch are written before the head\u0027s own bit is\npersistent. The wrong comparison can only shorten the wait, never\nlengthen it: across the wrap, the numerically larger value is always the\nolder one.\n\n5. Why not an earlier or later commit\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 72626685dc66d / ae3c20ccf84c8 (2005/2006): they chose int counters\n   and wrote every comparison in wrap-safe form. The design was correct,\n   so they aren\u0027t the cause.\n\n - 59fc630b8b5f9f (v4.1-rc1, batching): contains no bm_seq merge at all\n(git log -G bm_seq doesn\u0027t list it). Its missing bitmap handling is a\ndifferent bug, the one 2b6b24574256c fixed. The wrap problem exists only\nin the code 2b6b24574256c added.\n\n - d0852df543e5a, cd5fc65338181 and later commits: none of them touch\n   the comparison.\n\n - f565925810cb8 (Chen Cheng, merged with the fix): adds\n   READ_ONCE(conf-\u003eseq_flush) for a KCSAN race on the same counters\n(Fixes: 7c13edc87510). It is related but separate, and the \"\u003e\"\ncomparison isn\u0027t involved.\n\n6. Summary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Introduced by: 2b6b24574256c (\"md/raid5: ensure whole batch is\n   delayed for all required bitmap updates.\") (NeilBrown, 2015-05-21,\n   v4.1-rc6), which added the non-wrap-safe \"bm_seq \u003e seq\" merge in\n   stripe_add_to_batch_list().\n\n - Fixed by: 00e93faf4cea9 (\"md/raid5: let stripe batch bm_seq\n   comparison wrap-safe\") (v7.2-rc1), which switches it to the\n   \"bm_seq - seq \u003e 0\" form used everywhere else in raid5.c.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "7bb2ef8e22d868e13e2aa1701aed72346c07d8eb",
      "tree": "431a9abecbc16bee3162e379b25b9c7e55a6668c",
      "parents": [
        "67c176fe7766b5cb26d09421d57568b884ca9b63"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 14:42:09 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98901: Add .vulnerable file\n\nVulnerable from: e2f34481b24db (\"cifsd: add server-side procedures for\nSMB3\"), by Namjae Jeon, 2021-03-16, first released in v5.15-rc1.\n\nEvery piece of logic that the fix 5015191096db (\"ksmbd: fix lease break\nand ack state handling\") rewrites was added in that commit. Since then\nthose lines have only been moved, reformatted or partly patched; none of\nthe later commits changed the faulty logic.\n\nSuggested tag:\n\n  Fixes: e2f34481b24d (\"cifsd: add server-side procedures for SMB3\")\n\nThe CVE\u0027s description and affected-files fields were empty, so this\nworks from the fix commit itself. It touches fs/smb/server/oplock.c and\nfs/smb/server/smb2pdu.c.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix corrects four connected defects in how ksmbd breaks SMB2 leases\nand processes the client\u0027s Lease Break Acknowledgment.\n\nA. check_lease_state() checked the wrong rule (smb2pdu.c)\n---------------------------------------------------------\n\nThe SMB2 rule is that the state the client acknowledges must be a\n*subset* of the server\u0027s break target (lease-\u003enew_state). The old code\ndid this instead:\n\n    if (lease-\u003enew_state \u003d\u003d (R|H) \u0026\u0026 !(req_state \u0026 W)) {\n            lease-\u003enew_state \u003d req_state;  /* side effect: overwrites the break target */\n            return 0;\n    }\n    if (lease-\u003enew_state \u003d\u003d req_state)     /* otherwise: exact match only */\n            return 0;\n    return 1;\n\n - Valid acks were rejected. If the target is R, the legal ack \"None\"\n   (0x0) fails with STATUS_REQUEST_NOT_ACCEPTED.\n\n - Invalid acks were accepted. If the target is RH, any value without\n   the W bit passes, including undefined bits such as 0x8. That value is\n   then written into lease-\u003enew_state.\n\n - The check ran too early. It was called before the breaking_cnt\n   check, so it could overwrite new_state even when no break was in\n   progress.\n\nThe fix adds smb2_lease_state_valid(), uses\n!(req_state \u0026 ~lease-\u003enew_state) (a subset test), and moves the check\nafter the breaking_cnt test.\n\nB. The acknowledged state was not applied directly\n   (smb21_lease_break_ack())\n--------------------------------------------------\n\nThe old handler classified the ack into one of the\nOPLOCK_{WRITE_TO_READ, READ_HANDLE_TO_READ, WRITE_TO_NONE,\nREAD_TO_NONE} transitions. The matching opinfo_*_to_*() helper then set\nlease-\u003estate \u003d lease-\u003enew_state and checked opinfo-\u003elevel. Many legal\nacks fall outside those cases:\n\n - Ack of H (0x2) to an RH target, from an RWH lease. It passes the RH\n   special case and reaches lease_change_type \u003d 0, which only logs\n\"unknown lease change\" and leaves ret \u003d 0. The lease stays RWH at level\nBATCH, the reply carries LeaseState \u003d RWH with success status, and the\nbreak is treated as finished.\n\n - Ack of 0x8 to an RH target. It goes to the \"bad lease state\" branch,\n   which picks OPLOCK_WRITE_TO_NONE. opinfo_write_to_none() returns 0,\n   so err (STATUS_INVALID_OPLOCK_PROTOCOL) is ignored. lease-\u003estate\n   becomes the client-chosen 0x8 and the reply reports success.\n\nThe fix replaces all of this with:\n\n    lease-\u003estate \u003d req-\u003eLeaseState;\n    lease-\u003enew_state \u003d NONE;\n    opinfo-\u003elevel \u003d smb2_map_lease_to_oplock(state);\n\nC. A failed ack still ended the break (err_out path)\n----------------------------------------------------\n\nEvery error path fell into the same cleanup as success:\nop_state \u003d OPLOCK_STATE_NONE, wake oplock_q, atomic_dec(\u0026breaking_cnt),\nwake oplock_brk. So a rejected ack released the waiters in\nwait_lease_breaking() / oplock_break() as if the client had complied,\nwhile the lease state stayed unchanged.\n\nThe same path ran when there was no break at all (op_state \u003d\u003d NONE or\nbreaking_cnt \u003d\u003d 0). In that case it decremented breaking_cnt below zero,\nand a negative count reads as \"true\" in later atomic_read(\u0026breaking_cnt)\nchecks.\n\nThe fix gives err_out only smb2_set_err_rsp() plus opinfo_put(), so the\nbreak stays pending.\n\nD. Leases holding W were skipped on writes and truncates (oplock.c)\n-------------------------------------------------------------------\n\n - In smb_break_all_levII_oplock(), any lease with a bit other than R/H\n   set was skipped:\n\n       if (brk_op-\u003eis_lease \u0026\u0026 (brk_op-\u003eo_lease-\u003estate \u0026 ~(R|H))) {\n               ksmbd_debug(OPLOCK, \"unexpected lease state(0x%x)\\n\", ...);\n               goto next;\n       }\n\nA lease that still held W was therefore never broken when another open\nwrote to or truncated the file.\n\n - smb2_map_lease_to_oplock() was incomplete: WH (0x6), W (0x4) and H\n   (0x2) all mapped to 0. The fix needs a correct mapping because it now\n   derives opinfo-\u003elevel from the acknowledged state.\n\n2. Tracing each piece back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nEach piece was traced with git log -L on the pre-fix version\n(5015191096db^) and compared against e2f34481b24db:\n\n - check_lease_state():\nHistory: only 64b39f4a2fd29 (checkpatch whitespace), then\ne2f34481b24db.\n   Present in e2f34481b24db: yes, identical logic.\n\n - smb2_map_lease_to_oplock():\n   History: only 64b39f4a2fd29 (whitespace), then e2f34481b24db.\n   Present in e2f34481b24db: yes, identical.\n\n - W-lease skip in smb_break_all_levII_oplock():\nHistory: 10 later commits touch the function; each one\u0027s diff was\ngrepped. The only changes to these lines are whitespace\n   (070fb21e5912b, 64b39f4a2fd29); the rest are UAF/refcount/conn fixes.\n   Present in e2f34481b24db: yes, identical.\n\n - lease-\u003enew_state computation in oplock_break():\nHistory: git log -G new_state shows only e2f34481b24db and three style\ncommits.\n   Present in e2f34481b24db: yes.\n\n - Transition-based ack processing, the \"bad lease state\" block, and\n   the err_out that decrements breaking_cnt:\n   History: see the timeline below.\n   Present in e2f34481b24db: yes.\n\nIn e2f34481b24db, smb21_lease_break_ack() already had the early\ncheck_lease_state() call, the lease_change_type switch, and this error\npath:\n\n    err_out:\n            opinfo-\u003eop_state \u003d OPLOCK_STATE_NONE;\n            wake_up_interruptible_all(\u0026opinfo-\u003eoplock_q);\n            atomic_dec(\u0026opinfo-\u003ebreaking_cnt);\n            wake_up_interruptible_all(\u0026opinfo-\u003eoplock_brk);\n            opinfo_put(opinfo);\n            smb2_set_err_rsp(work);\n\nLater commits that touched this code but did not introduce the bug\n------------------------------------------------------------------\n\n - 1a93084b9a898 (\"ksmbd: move fs/cifsd to fs/ksmbd\") and 38c8a9a520825\n   (\"smb: move client and server files to common directory fs/smb\") are\n   pure R100 renames.\n\n - cd80ce7e68f16 (\"ksmbd: don\u0027t update -\u003eop_state as\nOPLOCK_STATE_NONE on error\") (2023, v6.7-rc4) was a partial fix. It\nremoved only the op_state reset from err_out, and kept\natomic_dec(\u0026breaking_cnt) and the wake-ups. The break still ended on a\nfailed ack.\n\n - 658609d9a618d (\"ksmbd: avoid duplicate opinfo_put() call on error of\n   smb21_lease_break_ack()\") (v6.7-rc6) fixed a double\n   opinfo_put()/breaking_cnt decrement on the ret \u003c 0 path. That is a\n   separate bug, also from the original code.\n\n - 50f930db22365 (\"ksmbd: fix potential use-after-free in oplock/lease\n   break ack\") (2025, v6.16-rc6) merged the success and error tails into\n   one, which put op_state \u003d OPLOCK_STATE_NONE back on the error path.\n   That undid cd80ce7e68f16\u0027s partial fix. The breaking_cnt decrement on\n   failure was there the whole time, from e2f34481b24db onward.\n\nAbout f44158485826c (\"cifsd: add file operations\")\n--------------------------------------------------\n\nThis is the next commit in the same series (v5.15-rc1~11^2~5^2~92,\nagainst ~93 for the root commit). It added the write, truncate,\nalloc-size, zero-data and copychunk callers of\nsmb_break_all_levII_oplock() in vfs.c. Those are the \"writes and\ntruncates\" the fix\u0027s commit message mentions. The faulty filter itself\nis in e2f34481b24db, which already reached it via smb_break_all_oplock()\non overwrite opens and on locks. f44158485826c only added more callers.\n\n3. How the bug plays out (all possible from e2f34481b24db on)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Client A holds an RW lease (opinfo-\u003elevel \u003d EXCLUSIVE). Client B\n    opens the file, and smb_grant_oplock() calls\n    oplock_break(prev, LEVEL_II). That sets new_state \u003d R, increments\n    breaking_cnt, sets op_state \u003d OPLOCK_ACK_WAIT, sends the break and\n    waits in wait_lease_breaking().\n\n 2. A drops all caching and acks LeaseState \u003d 0, which is a legal\n    subset of R.\n\n 3. check_lease_state(): the target isn\u0027t RH and R !\u003d 0, so the ack is\n    rejected with STATUS_REQUEST_NOT_ACCEPTED.\n\n 4. err_out still decrements breaking_cnt, wakes oplock_brk/oplock_q,\n    and (in the original code and again after 50f930db22365) sets\n    op_state \u003d NONE. B\u0027s open continues as if the break had succeeded.\n    The server still records A as RW/EXCLUSIVE, while A believes it has\n    no lease.\n\n 5. B writes. ksmbd_vfs_write() calls smb_break_all_levII_oplock(),\n    which sees W in A\u0027s lease state, logs \"unexpected lease state\", and\n    skips it. If A had acked R instead, the server would now hold a\n    state that never matches what A and the server agreed on.\n\nThe H-only ack (lease keeps W at level BATCH) and the 0x8 ack\n(client-chosen state reported back with success) from section 1.B lead\nto the same kind of mismatch: the server\u0027s lease state and the client\u0027s\ncaching state disagree.\n\n4. Summary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Introduced by: e2f34481b24db (\"cifsd: add server-side procedures for\n   SMB3\") (Namjae Jeon, 2021-03-16, v5.15-rc1), the first release of\n   ksmbd. All of the code the fix replaces dates from this commit.\n\n - Partially mitigated by: cd80ce7e68f16 (v6.7-rc4), which removed only\n   the op_state reset from err_out; that was later undone by\n   50f930db22365 (v6.16-rc6).\n\n - Fixed by: 5015191096db (\"ksmbd: fix lease break and ack state\n   handling\"), which addresses all of it in one place: subset\n   validation, applying the acked state directly, keeping the break\n   pending on failure, a complete lease-\u003eoplock mapping, and no longer\n   skipping W-bearing leases on writes and truncates.\n\n     Fixes: e2f34481b24d (\"cifsd: add server-side procedures for SMB3\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "67c176fe7766b5cb26d09421d57568b884ca9b63",
      "tree": "0868a5824e76446a2e64f184fe50cfc00b0b4c56",
      "parents": [
        "ad5e3b08e3c0ea26273846eefa16548ca2d7c14c"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 14:33:43 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98900: Add .vulnerable file\n\nVulnerable from: dd3140588d955 (\"xen-gntalloc: Userspace grant\nallocation driver\"), by Daniel De Graaf, February 2011, first released\nin v3.0-rc1.\n\nThat commit created /dev/xen/gntalloc and its IOCTL_GNTALLOC_ALLOC_GREF\nhandler. The handler had three defects from the start, and they are the\nthree things the fix changes. Tracing gntalloc_ioctl_alloc() line by\nline with git blame on the parent of the fix (2299822f3f466^), every\nline the fix touches comes from dd3140588d955 except the kcalloc() line,\nand that line only had its spelling changed later. Its position before\nthe limit check never moved.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, 2299822f3f466 (\"xen/gntalloc: validate grant count before\nallocation\"), merged via next-20260701 and reviewed by Juergen Gross,\nchanges gntalloc_ioctl_alloc() in drivers/xen/gntalloc.c in three ways:\n\n A. Before the fix, kcalloc(op.count, sizeof(gref_ids[0]) /* 4 */,\n    GFP_KERNEL) runs before any check of op.count against limit. After\n    the fix, op.count is compared against READ_ONCE(limit) and rejected\n    with -ENOSPC before kcalloc() is called.\n    Defect: a user-controlled allocation size reaches the slab or page\n    allocator without any bound.\n\n B. Before the fix:  if (gref_size + op.count \u003e limit)\n    After the fix:   if (gref_size \u003e limit || op.count \u003e limit - gref_size)\n    Defect: the addition is 32-bit unsigned and can wrap past the check.\n\n C. Before the fix:  priv-\u003eindex +\u003d op.count * PAGE_SIZE;\n    After the fix:   priv-\u003eindex +\u003d (uint64_t)op.count * PAGE_SIZE;\n    Defect: on 32-bit kernels the multiplication is 32-bit and wraps\n    before it is added to the u64 index.\n\nREAD_ONCE(limit) is needed because limit is a module parameter with mode\n0644, so root can change it through sysfs while the ioctl is running.\n\n2. How the bug works\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe ioctl input\n---------------\n\nThe ioctl structure is defined in include/uapi/xen/gntalloc.h and has\nbeen the same since dd3140588d955:\n\n    struct ioctl_gntalloc_alloc_gref {\n            uint16_t domid;\n            uint16_t flags;\n            uint32_t count;       /* fully user-controlled, 0..0xFFFFFFFF */\n            uint64_t index;       /* OUT */\n            uint32_t gref_ids[1]; /* now gref_ids_flex[] */\n    };\n\nDefect A: allocation before the limit check (the main issue)\n------------------------------------------------------------\n\nThis is the code as it was originally added in dd3140588d955:\n\n    if (copy_from_user(\u0026op, arg, sizeof(op))) ...\n\n    gref_ids \u003d kzalloc(sizeof(gref_ids[0]) * op.count, GFP_TEMPORARY); /* (1) sized by user */\n    if (!gref_ids) { rc \u003d -ENOMEM; goto out; }\n\n    spin_lock(\u0026gref_lock);\n    do_cleanup();\n    if (gref_size + op.count \u003e limit) {                                 /* (2) only now checked */\n            spin_unlock(\u0026gref_lock);\n            rc \u003d -ENOSPC;\n            goto out_free;\n    }\n    gref_size +\u003d op.count;\n    op.index \u003d priv-\u003eindex;\n    priv-\u003eindex +\u003d op.count * PAGE_SIZE;                                /* (3) */\n    spin_unlock(\u0026gref_lock);\n\nThe driver exists to enforce a global cap (limit, default 1024) on how\nmany grant pages user space can pin. The header comment warns that a\nremote domain can keep pages mapped and so consume kernel memory, which\nis why the cap exists.\n\nThe ordering defeats that cap for the temporary gref_ids array:\n\n - With the default limit, the largest valid array is 1024 x 4 bytes \u003d\n   4 KiB.\n\n - The caller can pass any op.count up to 0xFFFFFFFF, which asks for\n   about 16 GiB. That request goes straight to kcalloc().\n\n   - Up to KMALLOC_MAX_SIZE (4 MiB with 4K pages and MAX_PAGE_ORDER \u003d\n     10) the allocation succeeds. The kernel allocates, zeroes and frees\n     up to 4 MiB of memory per call, about 1000 times more than the\n     limit allows, and then returns -ENOSPC.\n\n   - Above that size, kmalloc uses ___kmalloc_large_node() and then\n     __alloc_frozen_pages_noprof(). There,\n     WARN_ON_ONCE_GFP(order \u003e MAX_PAGE_ORDER, gfp) (mm/page_alloc.c:5280\n     at 2299822f3f466) fires, because the call does not pass\n     __GFP_NOWARN. Any process allowed to open the device can therefore\n     trigger a kernel WARN. On systems with panic_on_warn set, that\n     crashes the machine.\n\nThe fix closes this by rejecting op.count \u003e limit before calling\nkcalloc().\n\nDefect B: the limit check can wrap\n----------------------------------\n\nIn dd3140588d955, gref_size and limit were both declared static int,\nand op.count is uint32_t. Under C\u0027s usual arithmetic conversions,\n\"gref_size + op.count \u003e limit\" is evaluated as unsigned int:\n\n - The sum can wrap. For example, with gref_size \u003d 5 and op.count \u003d\n0xFFFFFFFC, the sum is 1, which is not greater than 1024, so the check\npasses. gref_size then becomes 1 and the accounting is corrupted.\n\n - If root set limit to a negative value, which module_param(limit,\n   int, 0644) accepted, it was converted to 0xFFFFFFFF and the check\n   could never fail.\n\nWith default settings, defect A partly masks this one: a count large\nenough to wrap would also make kcalloc() fail first. With a large\nadministrator-set limit, the wrap is reachable.\n\nThe prerequisite patch 51d111301a3ad (\"xen/gntalloc: make grant\ncounters unsigned\") changed the types to unsigned int / uint, so\nnegative limits are now rejected at parse time. The fix then replaces\nthe addition with a subtraction check that cannot wrap.\n\nDefect C: 32-bit index arithmetic\n---------------------------------\n\nPAGE_SIZE is unsigned long, so on 32-bit kernels op.count * PAGE_SIZE\nis a 32-bit product. It wraps when count is 2^20 or more (with 4K\npages), before being added to the 64-bit priv-\u003eindex. Overlapping\nfile_index values would make find_grefs() and mmap offsets ambiguous.\nThis was also present in dd3140588d955; the fix casts op.count to\nuint64_t first.\n\n3. Everything else that touched this function\n   \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nNone of these introduced the bug:\n\n - 21643e69a4c06 (\"xen-gntalloc: integer overflow in\n   gntalloc_ioctl_alloc()\"), Dan Carpenter, v3.2-rc3:\nkzalloc(sizeof * count) -\u003e kcalloc(count, sizeof). An earlier partial\nfix. It stopped the size multiplication from wrapping on 32-bit. Its\ncommit message already noted the gref_size + op.count wrap, but it left\nthe allocation before the check and left the addition as it was.\n\n - 8ca19a8937ad9 (\"xen/gntalloc: Change gref_lock to a mutex\"),\n   v3.3-rc1: spinlock -\u003e mutex. Locking primitive only.\n\n - 0ee931c4e31a5 (\"mm: treewide: remove GFP_TEMPORARY allocation\n   flag\"), v4.14: GFP_TEMPORARY -\u003e GFP_KERNEL. Flag change only.\n\n - bf5802238dc18 (\"xen/gntalloc: Replace UAPI 1-element array\"), Kees\nCook, 2024 (v6.8-rc5): gref_ids[1] -\u003e gref_ids_flex[]. UAPI\nflexible-array cleanup, after the vulnerable logic.\n\n - 51d111301a3ad (\"xen/gntalloc: make grant counters unsigned\"), 2026:\n   int -\u003e unsigned int. Prerequisite for the fix.\n\ngit blame on the pre-fix function attributes the copy_from_user(), the\n\"if (!gref_ids)\" handling, do_cleanup(), \"if (gref_size + op.count \u003e\nlimit)\", \"gref_size +\u003d op.count\", and \"priv-\u003eindex +\u003d op.count *\nPAGE_SIZE\" all to dd3140588d9551. The kcalloc() line is attributed to\n0ee931c4e31a5 (flag change only), and the original allocation sits in\nthe same place, ahead of the check, in dd3140588d955 itself.\n\n4. Summary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Introduced by: dd3140588d955 (\"xen-gntalloc: Userspace grant\n   allocation driver\") (v3.0-rc1, 2011). That commit allocated gref_ids\n   from the raw user-supplied op.count before enforcing the global\n   limit. It also wrote the limit check as an unsigned addition that can\n   wrap, using signed int limit and counter variables, and computed the\n   per-file index with a multiplication that wraps on 32-bit.\n\n - Partially mitigated by: 21643e69a4c06 (v3.2), which fixed only the\n   size-multiplication overflow.\n\n - Fixed by: 51d111301a3ad (prerequisite) and 2299822f3f466. The fix\n   has no Fixes: tag; the correct one would be:\n\n     Fixes: dd3140588d95 (\"xen-gntalloc: Userspace grant allocation driver\")\n\n - Affected kernels: every kernel since v3.0 built with\n   CONFIG_XEN_GRANT_DEV_ALLOC.\n\n - Impact: any process that can open /dev/xen/gntalloc can make the\n   kernel allocate up to about 4 MiB per call despite a 4 KiB effective\n   cap, or trigger a WARN_ON_ONCE in the page allocator, which is a\n   denial of service when panic_on_warn is set. If an administrator sets\n   limit very high, the wrap in the limit check also becomes reachable\n   and corrupts the gref_size count.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "ad5e3b08e3c0ea26273846eefa16548ca2d7c14c",
      "tree": "5d998073beaa3fa5104bde49d7a956c7a94216af",
      "parents": [
        "e54b1ab393670ac9c2dc24b95f212bb6ee80f9fa"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 14:27:52 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98899: Add .vulnerable file\n\nVulnerable from: f66ee0410b1c3 (\"netfilter: ipset: Fix \"INFO: rcu\ndetected stall in hash_xxx\" reports\"), by Jozsef Kadlecsik, February\n2020. It first shipped in v5.6-rc4 and was backported to 5.4.24 as\n5dd9488ae4107 (and to 5.5.8 as a469bab3386ae). Every line the fix\nremoves was written by that commit, and it is also the commit that made\nthose lines wrong.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n5d0c22e73656d (\"netfilter: ipset: mark the rcu locked areas properly\"),\nby Jozsef Kadlecsik, landed in v7.2-rc3. It touches two functions in\nnet/netfilter/ipset/ip_set_hash_gen.h:\n\n - mtype_gc(): it used to read h-\u003etable with spin_lock_bh(\u0026set-\u003elock) +\n   ipset_dereference_set(h-\u003etable, set), then bump t-\u003euref. Now it uses\n   rcu_read_lock_bh() + rcu_dereference_bh() and drops the RCU lock\n   right after the uref bump.\n\n - mtype_add(): it used to hold RCU past the uref bump, drop it around\n   mtype_gc_do(), and retake it to sum t-\u003ehregion[e].elements. Now RCU\n   ends right after atomic_inc(\u0026t-\u003euref).\n\nThe rule being applied: an RCU reader is needed only from loading\nh-\u003etable until t-\u003euref is raised. After that the uref count keeps the\ntable alive.\n\n2. How h-\u003etable is protected\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - The only writer is mtype_resize(). It runs under the nfnetlink ipset\n   mutex and does rcu_assign_pointer(h-\u003etable, t), then\n   synchronize_rcu(). The old table is freed by whoever drops orig-\u003euref\n   to zero.\n\n - Readers must load the pointer inside an RCU read-side section, or\n   under a lock the writer also holds, and raise uref before leaving it.\nOtherwise synchronize_rcu() does not order their increment before the\nresizer\u0027s atomic_dec_and_test(\u0026orig-\u003euref).\n\n3. Before the root-cause commit the design was correct\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n18f84d41d34fa3 (\"netfilter: ipset: Introduce RCU locking in hash:*\ntypes\"), v4.2-rc1, added the spin_lock_bh(\u0026set-\u003elock) line in\nmtype_gc(). At that point:\n\n - mtype_resize() held set-\u003elock across the table swap:\n\n\tspin_lock_bh(\u0026set-\u003elock);\n\t...\n\trcu_assign_pointer(h-\u003etable, t);\n\tspin_unlock_bh(\u0026set-\u003elock);\n\tsynchronize_rcu();\n\n - The core held set-\u003elock for every add/del.\n\n - gc took set-\u003elock and called mtype_expire(), which read h-\u003etable\n   with ipset_dereference_protected(h-\u003etable, set), an assertion that\n   set-\u003elock is held.\n\nHere set-\u003elock really was the writer-side lock, so the gc access was\ncorrect. 18f84d41d34fa3 is not the root cause.\n\n4. What f66ee0410b1c3 did\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nTo fix RCU stalls on huge sets, it replaced the single set-\u003elock with\nper-region locks and RCU. In the same diff it made three changes that\nconflict with each other.\n\na) It removed set-\u003elock from the only h-\u003etable writer:\n\n\t-\tspin_lock_bh(\u0026set-\u003elock);\n\t-\torig \u003d __ipset_dereference_protected(h-\u003etable, 1);\n\t+\torig \u003d ipset_dereference_bh_nfnl(h-\u003etable);\n\t...\n\t-\trcu_assign_pointer(h-\u003etable, t);\n\t-\tspin_unlock_bh(\u0026set-\u003elock);\n\t+\t/* There can\u0027t be any other writer. */\n\t+\trcu_assign_pointer(h-\u003etable, t);\n\t\tsynchronize_rcu();\n\nFrom then on set-\u003elock no longer guards h-\u003etable. In hash types it only\ncovers the cidr bookkeeping (mtype_add_cidr()/mtype_del_cidr()) and the\nresize backlog list h-\u003ead. Region-locked types skip it in the core via\nip_set_lock().\n\nb) It rewrote mtype_gc() to read h-\u003etable under that same lock. It\ndefined a new macro:\n\n\t#define ipset_dereference_set(p, set) \\\n\t\trcu_dereference_protected(p, lockdep_nfnl_is_held(NFNL_SUBSYS_IPSET) || \\\n\t\t\t\t\t     lockdep_is_held(\u0026(set)-\u003elock))\n\n   and used it in the new work-queue gc:\n\n\tspin_lock_bh(\u0026set-\u003elock);\n\tt \u003d ipset_dereference_set(h-\u003etable, set);\n\tatomic_inc(\u0026t-\u003euref);\n\t...\n\tspin_unlock_bh(\u0026set-\u003elock);\n\tmtype_gc_do(set, h, t, r);\n\n    - rcu_dereference_protected() is a plain load (no READ_ONCE(), no\n      dependency ordering). It is only valid when the condition names a\n      lock that excludes the writer. Here it races with the resizer\u0027s\n      rcu_assign_pointer().\n\n    - Because the gc worker holds set-\u003elock, the lockdep condition is\n      always true. CONFIG_PROVE_RCU can never flag this access, even\n      though the lock it trusts does nothing against the resizer.\n\n    - The gc worker never holds the nfnl mutex.\n\nc) It added an RCU unlock/relock around mtype_gc_do() in mtype_add():\n\n\trcu_read_lock_bh();\n\tt \u003d rcu_dereference_bh(h-\u003etable);\n\t...\n\tatomic_inc(\u0026t-\u003euref);\n\t...\n\tif (SET_WITH_TIMEOUT(set)) {\n\t\trcu_read_unlock_bh();\n\t\tmtype_gc_do(set, h, t, r);\n\t\trcu_read_lock_bh();\n\t}\n\t... t-\u003ehregion[e].elements ...\n\trcu_read_unlock_bh();\n\nThis contradicts itself. If t needed RCU after the uref bump, running\nmtype_gc_do(t) outside RCU would be a use-after-free. If uref pins t,\nwhich is the real design, retaking RCU is pointless. Either way the\nmarked RCU section does not match what actually protects the table.\n\n5. The race\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - CPU0 (ipset add that triggers a resize; holds the nfnl mutex, not\n   set-\u003elock): raises orig-\u003euref, copies the buckets, does\n   rcu_assign_pointer(h-\u003etable, new) and synchronize_rcu(), replays the\n   backlog. If atomic_dec_and_test(\u0026orig-\u003euref) hits zero, it calls\n   mtype_ahash_destroy(orig).\n\n - CPU1 (gc kworker): spin_lock_bh(\u0026set-\u003elock) does not block CPU0. It\n   does a plain load of h-\u003etable, which may be orig, then\n   atomic_inc(\u0026orig-\u003euref) and mtype_gc_do(orig).\n\nset-\u003elock gives no ordering here. The only thing preventing a freed\ntable is a side effect: spin_lock_bh() turns off bottom halves, and\nsince the RCU flavours were merged (v4.20), synchronize_rcu() also waits\nfor BH-disabled regions. On PREEMPT_RT, spin_lock implies\nrcu_read_lock(). So the gc worker happened to be an RCU reader, and the\ncode relied on that without saying so.\n\n6. Practical impact\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThere is no known crash report or reproducer for this specific issue.\nThe lore thread is a sashiko/LLM-driven cleanup series, and the earlier\nv2 4/9 (\"Extend the rcu locked area properly\") tried the opposite\napproach before this was reworked.\n\nWhat actually goes wrong:\n\n - a real data race (a plain load against rcu_assign_pointer(), which\n   KCSAN would report);\n\n - RCU lockdep checking is silenced by a lock that doesn\u0027t protect the\n   pointer;\n\n - table lifetime depends on an unstated, implicit RCU reader.\n\nThere is no real use-after-free on current kernels. This is more a\ncorrectness and hardening fix than an exploitable bug.\n\n7. Why it is f66ee0410b1c3 and nothing earlier or later\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Blame of the pre-fix code (5d0c22e73656d^) shows every removed or\n   moved line in both hunks came from f66ee0410b1c3. The only exception\n   is the spin_lock_bh(\u0026set-\u003elock)/spin_unlock_bh(\u0026set-\u003elock) pair in\n   mtype_gc(), which came from 18f84d41d34fa3, where it was correct\n   (section 3).\n\n - No commit between f66ee0410b1c3 and the fix restored set-\u003elock in\n   mtype_resize() or changed these sequences. That includes\n97f7cf1cd80ee, 27c5a095e2518, 8478a729c0462, 2358f7427ccd6,\ne4b4984e28c16 and 4a597a87e2e2f. At 5d0c22e73656d^, mtype_resize() still\nloads orig with ipset_dereference_bh_nfnl(h-\u003etable) and publishes the\nnew table with the \"There can\u0027t be any other writer.\"\nrcu_assign_pointer() followed by synchronize_rcu(), with no set-\u003elock\nanywhere in the function.\n\n - After the fix, ipset_dereference_set() has no users left; only its\n   definition remains. mtype_gc() was its only caller.\n\n - Both sides of the race exist from f66ee0410b1c3 itself. The same\n   commit turned the timer-based gc (mtype_gc(struct timer_list *)) into\n   the deferrable work-queue gc (mtype_gc(struct work_struct *),\nINIT_DEFERRABLE_WORK() + queue_delayed_work()) and took set-\u003elock out\nof mtype_resize(), and the core started skipping set-\u003elock for\nregion-locked types via ip_set_lock(). The RCU flavour consolidation\n(4c7e9c1434c6f, v4.20-rc1) was already in place, so the implicit\nBH-disabled RCU reader in the gc worker behaves the same at\nf66ee0410b1c3 as on current kernels.\n\n8. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nMainline v5.6-rc4 up to the fix in v7.2-rc3, plus 5.4.24 onward (and\n5.5.8 onward on the EOL 5.5.y branch). With f66ee0410b1c3 recorded,\nscripts/dyad --sha1 5d0c22e73656d --vulnerable f66ee0410b1c3 gives:\n\n5.6:f66ee0410b1c3481ee75e5db9b34547b4d582465:7.2:5d0c22e73656d050daffad10a2ba8765ce8441c8\n5.4.24:5dd9488ae41070b69d2f4acb580f77db5705f9ca:0:0\n5.5.8:a469bab3386aebff33c59506f3a95e35b91118fd:0:0\n\nThe fix was backported to 5.10.271 (67bd3d2350d17), 5.15.222\n(892399abd1a00), 6.1.189 (eee5172741b2c), 6.6.158 (ecb48c6b9edc7),\n6.12.111 (0c46c9ce24130) and 6.18.53 (ca9fab1ca1553). 5.4.y has the\nbuggy code but is not in the fixed list.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e54b1ab393670ac9c2dc24b95f212bb6ee80f9fa",
      "tree": "822bb984b8d1fe01cf6eae10e82c8e402b4ab994",
      "parents": [
        "191f184cada69f2926b775a49dc2178e6253f5b5"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 14:19:13 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98898: Add .vulnerable file\n\nVulnerable from: 1919696eedc1e (\"drm/amdgpu: enable SI DPM\"), by Maruthi\nBayyavarapu, merged for v4.9-rc1. That is the first commit where\nsi_dpm.c is compiled and wired into the SI IP blocks, so it is where the\nunguarded thermal schedule_work() first becomes reachable (see section\n8).\n\nThe defective code comes from 841686df9f7d2 (\"drm/amdgpu: add SI DPM\nsupport (v4)\"), by Maruthi Bayyavarapu, August 2016, also first shipped\nin v4.9-rc1. It has been there since the amdgpu SI DPM code was first\nwritten. No later commit added it or changed how it behaves. At that\ncommit si_dpm.c is not in the Makefile and nothing references\nsi_dpm_ip_funcs, so it is the defect origin rather than the vulnerable\nboundary. The Fixes: tag for the fix names the code origin:\n\n  Fixes: 841686df9f7d (\"drm/amdgpu: add SI DPM support (v4)\")\n\nAll three commits involved (841686df9f7d2, 1919696eedc1e and the SI\nPCI ID commit 78fbb6859b739) are in v4.9-rc1, so the affected-version\nfloor is v4.9 whichever one is recorded.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nf8922d5a94669 (\"drm/amd/pm/si: Don\u0027t schedule thermal work when queue\nisn\u0027t initialized\"), by Timur Kristóf, is a cherry-pick of bd018d36171a6\nand landed in v7.2-rc4 without a Fixes: tag. It makes a one-line change\nin drivers/gpu/drm/amd/pm/legacy-dpm/si_dpm.c:7696:\n\n\t-\tif (queue_thermal)\n\t+\tif (queue_thermal \u0026\u0026 amdgpu_dpm)\n\t \t\tschedule_work(\u0026adev-\u003epm.dpm.thermal.work);\n\nThe commit message says that with amdgpu.dpm\u003d0 the thermal work item is\nnever initialised, so nothing should be queued on it.\n\n2. The bug in today\u0027s code\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThree pieces of si_dpm.c interact (line numbers are from origin/master).\n\na) The interrupt handler is always installed.\n\nsi_dpm_early_init() (:8015) always calls si_dpm_set_irq_funcs(), which\npoints adev-\u003epm.dpm.thermal.irq.funcs at si_dpm_irq_funcs\n   (.process \u003d si_dpm_process_interrupt).\n\nb) The interrupt sources are always registered, but the work item is\nset up only later and only if DPM is on.\n\n   In si_dpm_sw_init() (:7800):\n\n\tret \u003d amdgpu_irq_add_id(adev, AMDGPU_IRQ_CLIENTID_LEGACY, 230,\n\t\t\t\t\u0026adev-\u003epm.dpm.thermal.irq);  // always\n\tret \u003d amdgpu_irq_add_id(adev, AMDGPU_IRQ_CLIENTID_LEGACY, 231,\n\t\t\t\t\u0026adev-\u003epm.dpm.thermal.irq);  // always\n\t...\n\tif (amdgpu_dpm \u003d\u003d 0)\n\t\treturn 0;     // \u003c-- early exit with dpm\u003d0\n\tret \u003d si_dpm_init_microcode(adev);\n\t...\n\tINIT_WORK(\u0026adev-\u003epm.dpm.thermal.work,\n\t\t  amdgpu_dpm_thermal_work_handler);  // never reached\n\nc) The handler queued the work without checking anything.\n\n   si_dpm_process_interrupt() sets queue_thermal \u003d true for source IDs\n230 (thermal low-\u003ehigh) and 231 (thermal high-\u003elow), then called\nschedule_work(\u0026adev-\u003epm.dpm.thermal.work).\n\nThis combination is reachable for three reasons:\n\n - The SI SMU block is always added for Tahiti, Pitcairn, Verde, Oland\n   and Hainan (amdgpu/si.c:2692/2710/2727), so sw_init always runs and\n   IDs 230/231 are always registered.\n\n - amdgpu_irq_dispatch() calls src-\u003efuncs-\u003eprocess() for any registered\n   source. It does not check whether the source was enabled with\n   amdgpu_irq_get(). This was already true in 2016.\n\n - With dpm\u003d0, si_dpm_hw_init() returns right away. The driver never\n   programs or masks CG_THERMAL_INT and never sets the temperature\n   range. Whether a thermal interrupt fires therefore depends on the\nVBIOS/hardware state of the thermal mask and thresholds. A thermal\nevent can reach the IH ring and be dispatched to\nsi_dpm_process_interrupt().\n\n3. What happens when it triggers\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nadev is allocated zeroed (kzalloc in 2016, devm_drm_dev_alloc today),\nso thermal.work is all zeros: func \u003d\u003d NULL, data \u003d\u003d 0, and a lockdep map\nthat was never initialised.\n\n 1. schedule_work() -\u003e queue_work_on(): setting\n    WORK_STRUCT_PENDING_BIT succeeds because the word is 0.\n\n 2. __queue_work(): with CONFIG_DEBUG_OBJECTS_WORK, ODEBUG complains\n    about activating an untracked object. The return value is ignored,\n    so queueing continues and the work is linked onto the system\n    workqueue\u0027s pool.\n\n 3. A kworker runs process_one_work(). It copies work-\u003elockdep_map\n    (with lockdep enabled, the uninitialised key produces warnings),\n    then sets worker-\u003ecurrent_func \u003d work-\u003efunc (NULL) and calls\n    worker-\u003ecurrent_func(work).\n\n 4. Result: a NULL function-pointer call (RIP 0) in kworker context,\n    which is a kernel oops, or a panic with panic_on_oops. The code\n    path is the same in v4.9\u0027s process_one_work().\n\n4. How the defect origin wrote the bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe original radeon code was safe. The amdgpu SI DPM is a port of\nradeon\u0027s SI DPM, a9e61410921bc (\"drm/radeon/kms: add dpm support for SI\n(v7)\", v3.11). In radeon:\n\n - the thermal IV handling lived in si_irq_process() and was guarded\n   from the start:\n\n\tif (queue_thermal \u0026\u0026 rdev-\u003epm.dpm_enabled)\n\t\tschedule_work(...);\n\n   (radeon\u0027s r600 and evergreen DPM have the same guard);\n\n - INIT_WORK(\u0026rdev-\u003epm.dpm.thermal.work, ...) is only in\n   radeon_pm_init_dpm(), so the guard exactly matches the condition\n   under which the work is initialised.\n\nThe amdgpu port dropped the guard. 841686df9f7d2 added the 7,986-line\ndrivers/gpu/drm/amd/amdgpu/si_dpm.c built in amdgpu\u0027s IP-block style,\nwhere each IP owns its interrupt sources and handler. In that commit:\n\n - si_dpm_early_init() always installs the handler (si_dpm.c:7930,\n   funcs assigned at :7984);\n\n - si_dpm_sw_init() always registers sources 230/231 (:7705, :7709),\n   returns early when amdgpu_dpm \u003d\u003d 0 (:7723), and only after that runs\n   INIT_WORK (:7730);\n\n - si_dpm_process_interrupt() has\n\n\tif (queue_thermal)\n\t\tschedule_work(\u0026adev-\u003epm.dpm.thermal.work);\n\n   (:7620), with no dpm_enabled or amdgpu_dpm check.\n\nSo the commit wrote a path that queues the work without the work ever\nhaving been initialised, and left out the check radeon had always relied\non.\n\nThe IP-block skeleton was copied from amdgpu\u0027s existing CIK\nci_dpm.c/kv_dpm.c (a2e73f56fa628, 2015), which have the same unguarded\nshape. That is only where the pattern came from; the SI code that this\nCVE fixes was written in 841686df9f7d2.\n\n5. Nothing in between changed the behaviour\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe whole rename chain (amdgpu/si_dpm.c -\u003e pm/powerplay/si_dpm.c -\u003e\npm/legacy-dpm/si_dpm.c) was searched for any change to the\n\"if (queue_thermal\" line, the INIT_WORK, the amdgpu_dpm \u003d\u003d 0 early\nreturn, or the amdgpu_irq_add_id(...230/231) calls. Before the fix there\nwere only four hits, all cosmetic:\n\n  Commit          Change\n-------------- -------------------------------------------------\nd766e6a393383 amdgpu_irq_add_id() gains a client ID\n                  (AMDGPU_IH_CLIENTID_LEGACY)\n1ffdeca64856e Rename to AMDGPU_IRQ_CLIENTID_LEGACY e098bc9612c2b File\nmoved amdgpu/ -\u003e pm/powerplay/ (100% rename)\n  837d542a09cd5   File moved pm/powerplay/ -\u003e pm/legacy-dpm/\n                  (100% rename)\n\nNone of them moved the early return, the INIT_WORK, or the handler\nlogic. The code f8922d5a94669 patches is exactly what 841686df9f7d2\nwrote.\n\n6. Why the fix checks amdgpu_dpm rather than dpm_enabled\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nINIT_WORK depends only on amdgpu_dpm !\u003d 0. dpm_enabled goes false in\nsituations where the work *is* initialised: si_dpm_enable() failing in\nhw_init, and the window during suspend. So amdgpu_dpm is the exact\ncondition that guarantees the work item is valid.\n\n7. Related, outside this CVE\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - pm/legacy-dpm/kv_dpm.c:3007/3010/3183 in origin/master still has the\n   identical pattern: the amdgpu_dpm \u003d\u003d 0 early return before\n   INIT_WORK, and an unguarded \"if (queue_thermal) schedule_work(...)\".\n\n - With dpm\u003d0, si_dpm_sw_fini() (flush_work) and si_dpm_suspend()\n(cancel_work_sync) also run on the uninitialised work. Today that hits\nWARN_ON(!work-\u003efunc) in __flush_work(); it\u0027s a warning, not a crash.\n\n8. Code origin vs. vulnerable boundary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe SI support series landed in this order, all merged for v4.9-rc1:\n\n  841686df9f7d2  drm/amdgpu: add SI DPM support (v4)\n  84b77336eea36  drm/amdgpu: use DRM print instead of printk\n  1919696eedc1e  drm/amdgpu: enable SI DPM\n295d0dafd31c9 drm/amdgpu: Add SI Family information a8c65c137884f\ndrm/amdgpu/si: Add updated smc firmware for SI kickers\n  78fbb6859b739  drm/amdgpu: add si pciids v2\n\n841686df9f7d2 (and 84b77336eea36): code written, not reachable.\n - The Makefile line is still \"si.o gmc_v6_0.o gfx_v6_0.o si_ih.o\n   si_dma.o dce_v6_0.o\", with no si_dpm.o, so si_dpm_process_interrupt()\n   is not in the binary at all.\n - Nothing outside si_dpm.c/si_dpm.h references si_dpm_ip_funcs. The\nSMC entry in si.c\u0027s verde_ip_blocks is commented out and\nhainan_ip_blocks uses si_null_ip_funcs.\n - No binding method can help, because the handler does not exist.\n\n1919696eedc1e: code built, wired, and reachable.\n - It adds si_dpm.o and si_smc.o to the Makefile, points\n   amdgpu_powerplay_init()\u0027s non-powerplay branch at si_dpm_ip_funcs for\n   all five SI chips, and puts amdgpu_pp_ip_funcs in the SMC slot of\n   verde_ip_blocks and hainan_ip_blocks.\n - amdgpu_drv.c\u0027s pciidlist has no SI entries yet (they arrive in\n   78fbb6859b739), and pci new_id can\u0027t add one usefully: it only\n   accepts a driver_data that matches an existing entry, and no entry\n   carries CHIP_TAHITI..CHIP_HAINAN (enum values 0-4).\n - PCI driver_override does bind it. Since 782a985d7af26 (\"PCI:\nIntroduce new device binding path using pci_dev.driver_override\",\nv3.16), writing \"amdgpu\" to a Tahiti GPU\u0027s driver_override makes\npci_match_device() return pci_device_id_any when no table entry matches.\nThat dummy ID has driver_data 0, and in amd_asic_type\n   CHIP_TAHITI \u003d 0.\n - amdgpu_pci_probe() takes flags \u003d ent-\u003edriver_data \u003d 0. That has no\nAMD_EXP_HW_SUPPORT bit, so probe continues, and amdgpu_device_init()\nsets asic_type \u003d flags \u0026 AMD_ASIC_MASK \u003d CHIP_TAHITI.\n - From there the whole path from section 2 is live:\n    - amdgpu_early_init() sends CHIP_TAHITI to si_set_ip_blocks()\n      (verde_ip_blocks, now with amdgpu_pp_ip_funcs as SMC);\n    - amdgpu_pp_early_init() leaves pp_enabled false for SI (default\n      case), so amdgpu_powerplay_init() picks si_dpm_ip_funcs, and\n      si_dpm_early_init() installs si_dpm_irq_funcs;\n    - amdgpu_pp_sw_init() calls si_dpm_sw_init() unconditionally,\n      which registers sources 230/231 before the amdgpu_dpm \u003d\u003d 0\n      return;\n    - nothing outside the per-IP files checks amdgpu_dpm, so dpm\u003d0\n      does not drop the SMC block;\n    - amdgpu_irq_dispatch() calls src-\u003efuncs-\u003eprocess() for any\n      registered source, with no enabled check;\n    - the si_dpm.c lines involved (if (queue_thermal), INIT_WORK, the\n      amdgpu_dpm \u003d\u003d 0 return, amdgpu_irq_add_id(...230/231)) are\n      unchanged from 841686df9f7d2.\n - The only SI piece still missing is adev-\u003efamily \u003d AMDGPU_FAMILY_SI,\n   added by 295d0dafd31c9. That doesn\u0027t change the init path. At\n1919696eedc1e adev-\u003efamily is used only for AMDGPU_INFO, the debugfs\nconfig dump, powerplay\u0027s chip_family (not used for SI), and three\ncomparisons against CHIP_OLAND/CHIP_HAINAN in si_program_aspm(). Those\ncomparisons come out the same for family 0 as for AMDGPU_FAMILY_SI\n(110).\n\n78fbb6859b739: makes it reachable without driver_override.\n - It adds 72 SI PCI IDs to pciidlist under CONFIG_DRM_AMDGPU_SI\n   (Tahiti 13, Pitcairn 12, Verde 24, Oland 17, Hainan 6), none flagged\n   AMD_EXP_HW_SUPPORT, so SI GPUs bind automatically.\n - This widens exposure but does not create the bug, which was already\n   reachable at 1919696eedc1e.\n\nSo 1919696eedc1e is the first commit where an SI GPU booted with\namdgpu.dpm\u003d0 can take a thermal IV into si_dpm_process_interrupt() and\nqueue the uninitialised work.\n\n9. Vulnerable range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n1919696eedc1e appears only once in mainline (v4.9-rc1), so there is no\nearlier mainline cherry-pick to prefer. With it recorded, scripts/dyad\n--sha1 f8922d5a94669 --vulnerable 1919696eedc1e gives the mainline pair:\n\n4.9:1919696eedc1ed5c3ace229576e4fa322fb256dd:7.2:f8922d5a946699fc2bdc7660e6778bd6726bf8b8\n\nThat is the same 4.9 floor dyad gives for 841686df9f7d2 and\n78fbb6859b739. Every stable branch from v4.9 on is affected. The\npublished dyad records the fix in 5.10.271 (2c08ae91df3cf), 5.15.222\n(7987c5abdf537), 6.1.189 (8c9e7265b3379), 6.6.158 (2750f52b58d92),\n6.12.111 (ac0250da54afb), 6.18.53 (52c4c623474df) and 7.2\n(f8922d5a94669).\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "191f184cada69f2926b775a49dc2178e6253f5b5",
      "tree": "1c6bee5c12b433424634e6f92ccf6cb882a83072",
      "parents": [
        "cf5ed13fb00208e846f107437b55e0966412b9fe"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 13:57:56 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98897: Add .vulnerable file\n\nVulnerable from: d1c189c6cb8b0 (\"ksmbd: use rwsem instead of rwlock for\nlease break\"), by Namjae Jeon, May 2024, first released in v6.9, tagged\nCc: stable.\n\nSecondary: 18b4fac5ef17f (\"ksmbd: fix use-after-free in\nsmb_break_all_levII_oplock()\"), v6.15-rc3. It copied the same pattern\ninto the levII break path, which is the path in the fix\u0027s xfstests\nscenario.\n\nThe published dyad has 0:0 as the vulnerable commit because the fix,\n27a9bc968d4ae (\"ksmbd: don\u0027t hold ci-\u003em_lock while waiting for a lease\nbreak ack\"), has no Fixes: tag. The fixed versions listed are 6.18.53\nand 7.2.\n\n1. What the fix addresses\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nwait_for_break_ack() (oplock.c:702, pre-fix) sleeps on opinfo-\u003eoplock_q\nfor up to OPLOCK_WAIT_TIME (35 s). Two things end the wait: a lease\nbreak ack, or a close that sets OPLOCK_CLOSING and wakes the queue in\nclose_id_del_oplock().\n\nBefore the fix, three callers held ci-\u003em_lock for read across\noplock_break(), which is where that wait happens:\n\n  Function (pre-fix)                    down_read   oplock_break()\n                                                    under lock\n__smb_break_all_levII_oplock() :1616 :1653\nsmb_send_parent_lease_break_noti() :1300 :1317\nsmb_lazy_parent_lease_break_close() :1344 :1358\n\nThe close that should wake the waiter needs m_lock for write twice:\n\n - first in __ksmbd_close_fd() -\u003e __ksmbd_remove_fd(), which does\n   down_write(\u0026fp-\u003ef_ci-\u003em_lock) (vfs_cache.c);\n - then in close_id_del_oplock() -\u003e opinfo_del(), which does\n   down_write(\u0026ci-\u003em_lock) (oplock.c:343).\n\nThe writer blocks behind the reader, and the reader is waiting for that\nwriter. The break only ends when the 35 s timeout fires. Because rwsem\nqueues new readers behind a waiting writer, other operations on that\ninode that need m_lock (share-mode checks, opens) also stall.\n\nThe fix collects the target opinfos under the lock, each pinned with a\nreference. It then releases m_lock and only after that calls\noplock_break() on each one.\n\n2. How a client triggers it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ncifs.ko never sends an ack for these breaks; it closes the handle\ninstead.\n\nFile case (the fix\u0027s generic/001 scenario):\n\n 1. cifs.ko holds a read-handle (RH) lease on file F through a\n    deferred-close handle.\n 2. An O_TRUNC open arrives. smb2_open() calls smb_break_all_oplock()\n    before smb_grant_oplock(), so op \u003d opinfo_get(fp) is NULL and the\n    same-lease-key skip doesn\u0027t apply.\n 3. __smb_break_all_levII_oplock() takes down_read(\u0026ci-\u003em_lock) and\n    calls oplock_break(brk_op, NONE).\n 4. The lease has the handle bit set, so it moves to OPLOCK_ACK_WAIT.\n    smb2_lease_break_noti() sends a break with ACK_REQUIRED and calls\n    wait_for_break_ack().\n 5. cifs.ko closes the deferred handle. smb2_close() reaches\n    down_write(\u0026ci-\u003em_lock) in __ksmbd_remove_fd() and blocks. The\n    waiter times out after 35 s.\n\nDirectory case: a client holds an RH lease on directory D. Directory\nleasing has been advertised since 18dd1c367c31d (\"ksmbd: set v2 lease\ncapability\", v6.7-rc6).\n\n 1. A v2-lease open in D runs smb_send_parent_lease_break_noti(), which\n    breaks D\u0027s lease while holding p_ci-\u003em_lock. A close with\n    reserve_lease_break set does the same through\n    smb_lazy_parent_lease_break_close().\n 2. cifs.ko\u0027s cached_dir_lease_break() queues put_work, which closes\n    the cached directory handle and sends no ack.\n 3. That close blocks on down_write(\u0026D_ci-\u003em_lock), and the result is\n    the same 35 s stall.\n\n3. History\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - e2f34481b24db (\"cifsd: add server-side procedures for SMB3\"), v5.15:\n   not affected.\n    - m_lock was a rwlock_t, a spinning lock you cannot sleep under.\n    - smb_break_all_levII_oplock() walked m_op_list under RCU. It\n      pinned the entry, called rcu_read_unlock(), and only then called\n      oplock_break().\n\n - d47d9886aeef7 (\"ksmbd: send v2 lease break notification for\n   directory\") and c2a721eead712 (\"ksmbd: lazy v2 lease break on\n   smb2_write()\"), v6.7-rc6: not affected.\n    - These added the two parent-lease-break functions.\n    - Both dropped the lock around the break:\n\n\tread_unlock(\u0026p_ci-\u003em_lock);\n\toplock_break(...);\n\tread_lock(\u0026p_ci-\u003em_lock);\n\n      Sleeping under the rwlock wasn\u0027t possible anyway.\n\n - d1c189c6cb8b0 (\"ksmbd: use rwsem instead of rwlock for lease\n   break\"), v6.9: introduces the bug.\n    - It changed m_lock from rwlock_t to struct rw_semaphore, which made\n      it legal to sleep while holding it.\n    - In both parent-lease-break functions it deleted the\n      read_unlock/read_lock pair around oplock_break(). A down_read now\n      stays held for the whole loop, including wait_for_break_ack().\n    - The same commit made the close-path takers down_write:\n      opinfo_del() and __ksmbd_remove_fd(). That completes the\n      reader-waits-for-writer cycle.\n    - The stall was reachable right away. At this commit,\n      oplock_break() sets OPLOCK_ACK_WAIT for any lease with the handle\n      bit, smb2_lease_break_noti() then calls wait_for_break_ack(), and\n      RH directory leases were already being granted.\n    - The commit message states the intent outright: \"lease break wait\n      for lease break acknowledgment. rwsem is more suitable than\n      unlock while traversing the list.\" In other words, holding m_lock\n      across the ack wait was deliberate. That is exactly what the\n      fix\u0027s subject line reverses.\n\n - 18b4fac5ef17f (\"ksmbd: fix use-after-free in\n   smb_break_all_levII_oplock()\"), v6.15-rc3: extends the bug to the\n   levII path.\n    - To fix a UAF from unlocking mid-loop, it replaced the RCU walk in\n      smb_break_all_levII_oplock() with down_read(\u0026ci-\u003em_lock) held\n      across the loop, including oplock_break().\n    - At that commit an RH lease passes the state checks (RH maps to\n      LEVEL_II), gets OPLOCK_ACK_WAIT, and waits up to 35 s under the\n      lock. That is the headline scenario.\n    - This change was only possible because d1c189c6cb8b0 had already\n      made m_lock a sleeping lock.\n\n - 27a9bc968d4ae (\"ksmbd: don\u0027t hold ci-\u003em_lock while waiting for a\n   lease break ack\"), v7.2-rc2: the fix. It undoes the pattern at all\n   three sites.\n\n4. Why d1c189c6cb8b0 is the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - It is the first commit where ci-\u003em_lock is held while waiting for a\n   lease break ack. Before it, that was impossible because the lock was\n   a spinning rwlock and every caller dropped it before breaking.\n - Two of the fix\u0027s three changes undo exactly what it did in the\n   parent-lease paths.\n - The third site exists because 18b4fac5ef17f reused the sleepable\n   lock and the hold-across-the-break approach that d1c189c6cb8b0\n   introduced.\n\n5. Vulnerable range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nv6.9 through v7.2-rc1 via the directory-lease paths, and from v6.15-rc3\nvia the levII file path.\n\nSuggested tags for the fix:\n\n  Fixes: d1c189c6cb8b0 (\"ksmbd: use rwsem instead of rwlock for lease break\")\n  Fixes: 18b4fac5ef17f (\"ksmbd: fix use-after-free in smb_break_all_levII_oplock()\")\n\nBoth are Cc: stable, so they are likely present in older stable\nbranches as well. d1c189c6cb8b0 appears only once in mainline, so there\nis no earlier mainline cherry-pick to prefer. With it recorded,\nscripts/dyad --sha1 27a9bc968d4ae --vulnerable d1c189c6cb8b0 gives:\n\n6.9:d1c189c6cb8b0fb7b5ee549237d27889c40c2f8b:7.2:27a9bc968d4ae809a3f5ca2f2b136856ee613cb4\n  6.6.35:16ece7c5645a68efca22a0a69a7718c8e50c3232:0:0\n\n16ece7c5645a6 is the 6.6.y backport of d1c189c6cb8b0 (\"[ Upstream\ncommit d1c189c6cb8b0fb7b5ee549237d27889c40c2f8b ]\"), which means 6.6.y\nfrom 6.6.35 on is affected and has no fix yet.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "cf5ed13fb00208e846f107437b55e0966412b9fe",
      "tree": "574a805d2246563e17758ae9a3decc59746e3485",
      "parents": [
        "3d0fd8fa359d89151403f396e424c04fe7e82ef7"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 13:48:56 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98896: Add .vulnerable file\n\nVulnerable from: d6cc51cd1a4ae (\"thunderbolt: Setup control channel\"),\nby Andreas Noever, first released in v3.17-rc1 (2014).\n\nThat commit wrote the line the fix changes, \"ev-\u003etb \u003d tb;\". It stores a\nplain struct tb * in a work item that runs later and never takes a\nreference on the domain. The line has kept that form ever since, through\none rename, one move into a helper and two minor edits.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ntb_queue_hotplug() builds a struct tb_hotplug_event and queues\ntb_handle_hotplug() on tb-\u003ewq. Before the fix, ev-\u003etb held a pointer to\nthe domain without owning it. The handler then used it straight away:\npm_runtime_get_sync(\u0026tb-\u003edev), mutex_lock(\u0026tb-\u003elock), and\ntb_priv(tb)-\u003ehotplug_active, which lives in the same allocation as\nstruct tb.\n\nThe fix, 138ec65b2c761 (\"thunderbolt: Keep the domain reference while\nprocessing hotplug\"), turns that pointer into an owned reference:\n\n\t-\tev-\u003etb \u003d tb;\n\t+\tev-\u003etb \u003d tb_domain_get(tb);\n\t...\n\t+\t/* Undo the refcount increased in tb_queue_hotplug() */\n\t+\ttb_domain_put(tb);\n\t \tkfree(ev);\n\nThis is the race it guards against, during driver unbind or shutdown\nwhile a router is being plugged in:\n\n 1. On the event side, the control-channel receive path runs\n    tb_ctl_rx_callback() -\u003e tb_domain_event_cb() -\u003e tb_handle_event()\n    -\u003e tb_queue_hotplug(), which queues a work item carrying the bare\n    tb pointer.\n 2. On the teardown side, nhi_remove() -\u003e tb_domain_remove() runs\n    tb_stop() and tb_ctl_stop(), then flush_workqueue(tb-\u003ewq), then\n    device_unregister(\u0026tb-\u003edev). When the last reference goes,\n    tb_domain_release() calls tb_ctl_free() and destroy_workqueue(),\n    then kfree(tb).\n 3. Nothing in the work item keeps tb alive. The only protection is\n    that teardown happens to drain tb-\u003ewq before the free. A hotplug\n    item queued or run outside that drained window dereferences a\n    freed struct tb.\n\nThe fix makes the work item own the domain, so tb cannot be freed\nbefore tb_handle_hotplug() finishes, whatever order teardown runs in.\n\n2. History of the \"ev-\u003etb \u003d tb\" line\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - d6cc51cd1a4ae (\"thunderbolt: Setup control channel\"), v3.17-rc1:\n   introduced it. Created\n\n\tstruct tb_hotplug_event {\n\t\tstruct work_struct work;\n\t\tstruct tb *tb;\n\t\t...\n\t};\n\n   and tb_schedule_hotplug_handler() with\n\"ev-\u003etb \u003d tb; queue_work(tb-\u003ewq, \u0026ev-\u003ework);\". tb_handle_hotplug() took\ntb-\u003elock and read tb-\u003ehotplug_active through that pointer.\n - 9d3cce0b61368 (\"thunderbolt: Introduce thunderbolt bus and\n   connection manager\"), v4.13-rc1: made struct tb a reference-counted\n   struct device. Split teardown into tb_domain_remove() (flush) and\n   tb_domain_release() (destroy the workqueue, then free). Copied\n   tb_schedule_hotplug_handler() unchanged and wired it up as\n   cm_ops-\u003ehotplug.\n - 81a54b5e1986d (\"thunderbolt: Let the connection manager handle all\n   notifications\"), v4.13-rc1: renamed the function to\n   tb_handle_event(); the \"ev-\u003etb \u003d tb\" line was kept as is.\n - 4f807e47ee9a7 (\"thunderbolt: Add support for Display Port tunnels\"),\n   v5.2-rc1: moved the block into the new tb_queue_hotplug() and added a\n   second caller from tb_scan_port() for DisplayPort hot-plug detect.\n   git blame stops here, but this is only a move.\n - d6d458d42e1e1 (\"thunderbolt: Handle DisplayPort tunnel activation\n   asynchronously\"), v6.14-rc1: switched to INIT_DELAYED_WORK and\n   queue_delayed_work(tb-\u003ewq, \u0026ev-\u003ework, 0). Ownership did not change.\n - 69050f8d6d075 (\"treewide: Replace kmalloc with kmalloc_obj for\n   non-scalar types\") / bf4afc53b77ae (\"Convert \u0027alloc_obj\u0027 family to\n   use the new default GFP_KERNEL argument\"), v7.0-rc1: changed kmalloc\n   to kmalloc_obj; cosmetic.\n - 138ec65b2c761 (the fix), v7.2-rc1: tb_domain_get() / tb_domain_put().\n\n3. Why d6cc51cd1a4ae and not a later commit\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe flaw is that the deferred hotplug work dereferences a domain\npointer it does not own. Its safety depends only on teardown ordering,\nand d6cc51cd1a4ae is where that design started. There,\nthunderbolt_shutdown_and_free() stopped and freed the control channel\nunder tb-\u003elock, cleared hotplug_active, then flushed and destroyed\ntb-\u003ewq, and only then called kfree(tb). The work item never pinned\nanything. Every later version relies on the same ordering, just split\nacross more functions.\n\n - 9d3cce0b61368 is the strongest alternative. It made the domain\n   reference-counted and introduced tb_domain_remove(), which the fix\u0027s\n   commit message names. But it copied the hotplug code without changes\n   and kept the same flush-then-destroy-then-free ordering. It made a\n   reference possible to take; it did not create the missing one.\n - 81a54b5e1986d and 4f807e47ee9a7 only renamed or moved the line.\n4f807e47ee9a7 did add a second place that queues hotplug work, but it\nis the same pattern.\n - 559c1e1e01343 (\"thunderbolt: Run tb_xdp_handle_request() in system\n   workqueue\"), v5.2, added tb_domain_get() itself. It took a reference\n   only because the XDomain request work moved off tb-\u003ewq onto the\n   system workqueue. That shows the rule the hotplug path never\n   followed: work that might outlive the drain must hold a domain\n   reference.\n\n4. How reachable this is\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nOn the tree before the fix, the use-after-free of struct tb is a narrow\nwindow, not a straightforward trigger:\n\n - tb_ring_stop() calls flush_work(\u0026ring-\u003ework), so no receive callback\n   is still running after tb_ctl_stop().\n - tb_domain_remove() then flushes tb-\u003ewq.\n - tb_domain_release() calls destroy_workqueue(), which drains the\n   queue before kfree(tb).\n - Hotplug items always use delay 0, so no pending timer can escape the\n   flush.\n\nHitting it needs an item queued after the flush. One example: when\nnhi-\u003egoing_away is set, tb_ring_stop() skips stopping the ring. In\npractice the fix replaces fragile, order-based safety with ownership.\n\n5. Related code the fix leaves alone (not part of this CVE)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - tb_queue_dp_bandwidth_request() (from 6ce3563520be9, \"thunderbolt:\nAdd support for DisplayPort bandwidth allocation mode\", v6.3) still\nsets ev-\u003etb \u003d tb without a reference, and it can queue with a non-zero\nretry delay. flush_workqueue() and destroy_workqueue() do not wait for a\npending delayed-work timer.\n - icm_handle_event() stores n-\u003etb \u003d tb the same way for the firmware\n   connection manager.\n\n6. Code origin vs. vulnerable boundary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nd6cc51cd1a4ae is both where the \"ev-\u003etb \u003d tb\" line came from and the\nfirst commit where the hotplug work item is queued and run against a\nlive domain. No later commit made the failure reachable in a way it was\nnot already at d6cc51cd1a4ae, so there is no later boundary to record.\n\nAt d6cc51cd1a4ae the path is built and live:\n\n - The commit adds tb.o to thunderbolt-objs next to nhi.o and ctl.o,\n   which CONFIG_THUNDERBOLT (tristate, \"Thunderbolt support for Apple\n   devices\") already builds.\n - nhi_probe() calls thunderbolt_alloc_and_start(), which calls\n   tb_ctl_alloc(tb-\u003enhi, tb_schedule_hotplug_handler, tb) and\n   tb_ctl_start(). nhi_remove() calls thunderbolt_shutdown_and_free().\n - A TB_CFG_PKG_EVENT frame from the controller goes ring_work() -\u003e\n   tb_ctl_rx_callback() -\u003e tb_ctl_handle_plug_event() -\u003e\n   ctl-\u003ecallback(), i.e. tb_schedule_hotplug_handler(), which queues\n   tb_handle_hotplug() on tb-\u003ewq with the bare tb pointer. The handler\n   does mutex_lock(\u0026tb-\u003elock) and reads tb-\u003ehotplug_active through it.\n\nThe protection is the same at d6cc51cd1a4ae and at the fix\u0027s parent\n(2fb199dc64056), three steps in the same order:\n\n - Stop the event source. At d6cc51cd1a4ae, tb_ctl_stop() -\u003e\n   ring_stop() clears ring-\u003erunning and does\n   schedule_work()/flush_work(\u0026ring-\u003ework). ring_work() then cancels\n   every queued frame, and tb_ctl_rx_callback() returns early on\n   canceled. At the fix\u0027s parent, tb_domain_remove() calls\n   cm_ops-\u003estop() (tb_stop() clears hotplug_active) and tb_ctl_stop()\n   under tb-\u003elock, and tb_ring_stop() does the same flush_work().\n - Drain: flush_workqueue(tb-\u003ewq) in both.\n - Free last. d6cc51cd1a4ae calls destroy_workqueue() and then\n   kfree(tb) in the same function. From 9d3cce0b61368 on,\n   tb_domain_release() calls tb_ctl_free() first. Its tb_ring_free()\n   unhooks the rings from the NHI so nhi_interrupt_work can no longer\n   reschedule ring-\u003ework, and then flushes ring-\u003ework.\n   destroy_workqueue() and kfree(tb) follow.\n\nIn every version, destroy_workqueue(tb-\u003ewq) and kfree(tb) are back to\nback in the same function, and every source of hotplug work has already\nbeen stopped. Work can only land on tb-\u003ewq after the drain if the queuer\nalready holds a stale tb.\n\nLater candidates checked against that:\n\n - 9d3cce0b61368 (v4.13-rc1) split teardown into tb_domain_remove() and\n   a refcounted tb_domain_release(). It is what the fix\u0027s message means\n   by \"removed by tb_domain_remove()\". It dropped none of the three\n   steps: the handler still cannot run once destroy_workqueue() has\n   returned, and kfree(tb) still comes after it. It made a reference\n   possible to take; it did not open a new path.\n - bdccf295d7cdf (\"thunderbolt: Do not touch the hardware if the NHI is\n   gone on resume\", v4.13) added nhi-\u003egoing_away, the example in section\n   4. It is only set when pci_device_is_present() fails on resume, that\n   is, after the controller has already disappeared. Even if an event\n   then gets past tb_ring_stop() and lands on tb-\u003ewq after the flush in\n   tb_domain_remove(), destroy_workqueue() in tb_domain_release() still\n   runs it before kfree(tb), and it bails at the hotplug_active check.\n   At worst it runs against a torn-down domain, not a freed one.\n - 4f807e47ee9a7 added the second queuer, from tb_scan_port() through\n   tb_scan_switch(). Its callers are tb_handle_hotplug() (already on\n   tb-\u003ewq and gated by hotplug_active), tb_start() and tb_complete().\n   Any item they queue is drained by the same flush and destroy.\n - d6d458d42e1e1 switched to delayed work, but tb_queue_hotplug()\n   always passes a delay of 0, so no timer can outlive the drain. (The\n   non-zero-delay case is tb_queue_dp_bandwidth_request(), section 5,\n   which is not part of this CVE.)\n\nSo the safety, which rests only on ordering and is just as fragile\neverywhere, is the same from d6cc51cd1a4ae up to the fix. The\naffected-version floor follows from that.\nscripts/dyad --sha1 138ec65b2c761 gives:\n\n - --vulnerable d6cc51cd1a4ae: 3.17 -\u003e 7.2\n - --vulnerable 9d3cce0b61368: 4.13 -\u003e 7.2\n - --vulnerable bdccf295d7cdf: 4.13 -\u003e 7.2\n\nThe only difference is v3.17 to v4.12, all long EOL. Every maintained\nstable branch (5.10, 5.15, 6.1, 6.6, 6.12, 6.18) is affected under all\nthree. Recording 9d3cce0b61368 or bdccf295d7cdf would claim v3.17 to\nv4.12 were safe, but they have the same live path behind the same\nprotection.\n\n7. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Vulnerable from: d6cc51cd1a4ae (\"thunderbolt: Setup control\n   channel\"), v3.17-rc1. It introduced the hotplug work item that\n   carries a bare, unreferenced struct tb pointer and relies only on\n   teardown ordering to keep the domain alive. It is both the code\n   origin and the vulnerable boundary: the path is live there, and no\n   later commit weakened the protection (section 6).\n - Not responsible: 9d3cce0b61368 (made the domain refcounted but\n   copied the hotplug code unchanged and kept drain-before-free),\n   bdccf295d7cdf (going_away; anything that gets past the flush is still\n   drained by destroy_workqueue() before kfree(tb)),\n   81a54b5e1986d (rename),\n4f807e47ee9a7 (move into tb_queue_hotplug() plus a second caller of the\nsame pattern), d6d458d42e1e1 (delayed work, delay 0),\n   69050f8d6d075 / bf4afc53b77ae (allocation helper changes).\n - Fixed by: 138ec65b2c761 (\"thunderbolt: Keep the domain reference\n   while processing hotplug\"), v7.2-rc1, which takes a domain reference\n   in tb_queue_hotplug() and drops it at the end of tb_handle_hotplug().\n - The fix is in stable 5.10 (06b2e894e640f), 5.15 (1d991f749a825),\n6.1 (e8656465d08ed), 6.6 (b61a4ae2ec6a0), 6.12 (04df2985159e3) and 6.18\n(0a87af3b1e3aa).\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "3d0fd8fa359d89151403f396e424c04fe7e82ef7",
      "tree": "1ae3504964167dd8595e663f697b240956475997",
      "parents": [
        "33ca76395a53ef30d241aadc90998eb47a9e8d1c"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 13:25:59 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98895: Add .vulnerable file\n\nVulnerable from: 9fc43b5e3933a (\"net/mlx5: fs, add HWS to steering mode\noptions\"), by Moshe Shemesh, first released in v6.14-rc1. That is the\nfirst commit where fs_core can select the HWS flow commands, so it is\nwhere the dangling-node bug first becomes reachable.\n\nThe defective code comes from 71a1372b8275d (\"net/mlx5: HWS, added\ntables handling\"), by Yevgeny Kliteynik, merged for v6.12-rc1. That\ncommit created the table \"default miss\" bookkeeping that links tables in\nboth directions. Its mlx5hws_table_destroy() never removed the table\nbeing destroyed from the list of the table it misses to. At that commit,\nand in all of v6.12 and v6.13, nothing outside the HWS table code calls\nmlx5hws_table_set_default_miss() or mlx5hws_table_destroy(). So it is\nthe defect origin rather than the vulnerable boundary (see section 5).\n\nThe fix, 60e9e82f162ab (\"net/mlx5: HWS, Handle destroying table that\nhas a miss table\"), adds exactly that missing step. It has no Fixes:\ntag. The tag it should carry names the code origin:\n\n\tFixes: 71a1372b8275 (\"net/mlx5: HWS, added tables handling\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n\t@@ int mlx5hws_table_destroy(struct mlx5hws_table *tbl)\n\t+\tif (tbl-\u003edefault_miss.miss_tbl)\n\t+\t\tlist_del_init(\u0026tbl-\u003edefault_miss.next);\n\t+\n\t \tlist_del_init(\u0026tbl-\u003etbl_list_node);\n\t \tmutex_unlock(\u0026ctx-\u003ectrl_lock);\n\t \thws_table_uninit(tbl);\n\t \tkfree(tbl);\n\nThe fix\u0027s commit message says that when a table that has a miss table\nis deleted, \"the node in miss_tbl list will contain garbage\". In other\nwords, a freed object is left linked into a live list. The upstream\nthread (cover letter \"Steering misc enhancements\" plus Simon Horman\u0027s\nReviewed-by) has no discussion of where the bug came from.\n\n2. The data structure\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n71a1372b8275d added this in mlx5hws_table.h (later renamed to table.h):\n\n\tstruct mlx5hws_default_miss {\n\t\t/* My miss table */\n\t\tstruct mlx5hws_table *miss_tbl; // forward pointer: src -\u003e dst\n\t\tstruct list_head next;  // src\u0027s node, linked on dst-\u003edefault_miss.head\n\t\t/* Tables missing to my table */\n\t\tstruct list_head head;  // dst\u0027s list of all srcs pointing at it\n\t};\n\nmlx5hws_table_set_default_miss(src, dst) sets the forward pointer and\nthen calls list_add(\u0026src-\u003edefault_miss.next, \u0026dst-\u003edefault_miss.head).\nSo every source table has a node embedded in its own memory, linked\ninto the destination table\u0027s list.\n\nThe destination walks that list in\nmlx5hws_table_update_connected_miss_tables():\n\n\tlist_for_each_entry(src_tbl, \u0026dst_tbl-\u003edefault_miss.head, default_miss.next)\n\t\tret \u003d mlx5hws_table_connect_to_miss_table(src_tbl, dst_tbl);\n\nThat walk reprograms each source\u0027s last flow table (FT) through\nfirmware and writes src_tbl-\u003edefault_miss.miss_tbl \u003d dst_tbl. It runs\nfrom matcher.c every time the first matcher of the destination is\nconnected or disconnected:\n\n - hws_matcher_connect() when !prev;\n - hws_matcher_disconnect() when prev_ft_id \u003d\u003d tbl-\u003eft_id.\n\nThat keeps each source\u0027s miss path pointing at the destination\u0027s first\nmatcher RTC (the hardware lookup table behind a matcher).\n\n3. How 71a1372b8275d introduced the defect\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe mlx5hws_table_destroy() it added handled only one side of the\nrelationship:\n\n\tif (!list_empty(\u0026tbl-\u003ematchers_list)) { ... -EBUSY }\n\n\tif (!list_empty(\u0026tbl-\u003edefault_miss.head)) { // am I a *destination*?\n\t\tmlx5hws_err(tbl-\u003ectx, \"Cannot destroy table pointed by default miss\\n\");\n\t\tret \u003d -EBUSY; goto unlock_err;\n\t}\n\n\tlist_del_init(\u0026tbl-\u003etbl_list_node);\n\tmutex_unlock(\u0026ctx-\u003ectrl_lock);\n\thws_table_uninit(tbl);\n\tkfree(tbl); // \u003c-- may still be linked as a *source*\n\n - As a destination, the table is protected: destroy is refused with\n   -EBUSY while other tables point at it.\n - As a source, nothing is checked or cleaned up. If\n   tbl-\u003edefault_miss.miss_tbl !\u003d NULL, then tbl-\u003edefault_miss.next is\n   still linked on miss_tbl-\u003edefault_miss.head. The kfree(tbl) frees\n   memory that is still part of another table\u0027s list.\n\nThe public API in mlx5hws.h does not require callers to reset the\ndefault miss before destroying a table. Nothing in the HWS layer\nenforces it either. So this is a defect in the destroy routine that\ncommit introduced.\n\nWhat happens after the table is freed\n-------------------------------------\n\nSay S is the freed source table and D is the table it missed to. D\u0027s\nlist still reaches the freed S-\u003edefault_miss.next.\n\n 1. Use-after-free read and write through matcher updates. Creating D\u0027s\n    first matcher, or destroying its first matcher, calls\n    mlx5hws_table_update_connected_miss_tables(D). That walks into freed\n    S:\n     - It reads S-\u003ectx, S-\u003ematchers_list and S-\u003efw_ft_type.\n     - It issues MODIFY_FLOW_TABLE commands using an ft_id taken from\n       freed memory.\n     - It then writes S-\u003edefault_miss.miss_tbl \u003d D into freed slab\n       memory.\n 2. Use-after-free write through list operations. A later\n    mlx5hws_table_set_default_miss(X, D) runs\n    list_add(\u0026X-\u003edefault_miss.next, \u0026D-\u003edefault_miss.head). That writes\n    head-\u003enext-\u003eprev, which is the freed S node. The original code also\n    ran list_del_init() on that head, which writes through the stale\n    neighbours.\n 3. D can never be destroyed. list_empty(\u0026D-\u003edefault_miss.head) stays\n    false, so mlx5hws_table_destroy(D) keeps returning \"Cannot destroy\n    table pointed by default miss\" (-EBUSY). D\u0027s memory and its firmware\n    flow table leak.\n\n4. Later commits to table.c\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log --follow on table.c:\n\n - 71a1372b8275d (\"net/mlx5: HWS, added tables handling\"), v6.12-rc1:\n   introduces the list bookkeeping and the incomplete destroy.\n - 510f9f61a1121 (\"net/mlx5: HWS, added API and enabled HWS support\"),\n   v6.12-rc1, same merge: adds the Kconfig/Makefile hooks so the code is\n   compiled. Destroy logic unchanged.\n - 3f4c38df5b0f5 (\"net/mlx5: HWS, fixed error flow return values of\n   some functions\"), a2740138ec659 (\"net/mlx5: HWS, renamed the files in\n   accordance with naming convention\"), 0a1ef807a403b (\"net/mlx5: HWS,\n   denote how refcounts are protected\"), c86963aae5b83 (\"net/mlx5: HWS,\n   simplify allocations as we support only FDB\"): return values, file\n   rename, comments, allocations. Not related.\n - ad4da6cc36ace (\"net/mlx5: HWS, remove wrong deletion of the miss\n   table list\"), v6.14-rc1: changes list semantics only (details below).\n   Does not touch destroy.\n - 0f3ecf5c57d86 (\"net/mlx5: fs, add HWS flow table API functions\"),\n   v6.14-rc1: first in-tree caller of the table API (details below and\n   in section 5).\n - d2338a27fcee9 (\"net/mlx5: HWS, expose function\n   mlx5hws_table_ft_set_next_ft in header\"), 8a51507320ebd (\"net/mlx5:\nHWS, Fix table creation UID\"), 69050f8d6d075 (\"treewide: Replace\nkmalloc with kmalloc_obj for non-scalar types\"), bf4afc53b77ae\n(\"Convert \u0027alloc_obj\u0027 family to use the new default GFP_KERNEL\nargument\"): not related.\n - 60e9e82f162ab (\"net/mlx5: HWS, Handle destroying table that has a\n   miss table\"), v7.2-rc1: the fix.\n\nA pickaxe search (git log -S \u0027default_miss.next\u0027) on origin/master\nfinds only 71a1372b8275d and the fix. Nothing before 71a1372b8275d\ncontains this code. The older SW steering code (dr_table.c) is a\nseparate implementation with no default-miss list.\n\nad4da6cc36ace changes how the list is kept, not whether the destroy bug\nexists\n-----------------------------------------------------------------------\n\nThe original mlx5hws_table_set_default_miss() had:\n\n\t// re-read AFTER connect \u003d\u003e this is the NEW miss_tbl\n\told_miss_tbl \u003d tbl-\u003edefault_miss.miss_tbl;\n\tif (old_miss_tbl)\n\t\tlist_del_init(\u0026old_miss_tbl-\u003edefault_miss.head);\n\tif (miss_tbl)\n\t\tlist_add(\u0026tbl-\u003edefault_miss.next, \u0026miss_tbl-\u003edefault_miss.head);\n\nEach call unhooked the new destination\u0027s list head before adding the\nsource. As a result, D\u0027s list held only the most recently attached\nsource, and earlier sources were left in a ring with no head.\nad4da6cc36ace removed those lines so D tracks all of its sources.\n\nIn both versions, a destroyed source with a non-NULL miss_tbl stays\nlinked on D\u0027s list. In the original code it is D\u0027s only entry, so the\nconsequences in section 3 apply in full. So ad4da6cc36ace neither\nintroduced nor fixed this bug. It is an ancestor of 9fc43b5e3933a, so at\nthe vulnerable boundary D\u0027s list already tracks every source.\n\nHow the fs_core path reaches the dangling node\n----------------------------------------------\n\n0f3ecf5c57d86 wired fs_core to HWS. mlx5_cmd_hws_destroy_flow_table()\ndoes try to detach first:\n\n\terr \u003d mlx5_fs_set_ft_default_miss(ns, ft, NULL);\n\tif (err)\n\t\tmlx5_core_err(ns-\u003edev, \"Failed to disconnect next table (%d)\\n\", err);\n\n\terr \u003d mlx5hws_table_destroy(ft-\u003efs_hws_table.hws_table); // proceeds anyway\n\nThis sequence is unchanged from 9fc43b5e3933a up to the fix\u0027s parent.\n\nmlx5hws_table_set_default_miss(tbl, NULL) only unlinks after\nmlx5hws_table_connect_to_miss_table() succeeds, because that is where\nthe firmware MODIFY_FLOW_TABLE commands are issued. If any of those\ncommands fails:\n\n - miss_tbl stays set;\n - the node stays on D\u0027s list;\n - fs_hws logs the error and destroys the table anyway, which reaches\n   the dangling-node case in section 3.\n\nThe clear_ft_miss: -\u003e destroy_table: error path in\nmlx5_cmd_hws_create_flow_table() has the same pattern.\n\nOne detail on triggers: mlx5_internal_err_ret_value() makes\nMODIFY_FLOW_TABLE return success when the device is in internal-error\nstate. So this path needs a real firmware or command failure, not just a\ndevice that is down. Any direct HWS API user that destroys a source\ntable without resetting its miss table would hit the bug\nunconditionally. No such user exists in-tree: the only other caller of\nmlx5hws_table_destroy() at the fix\u0027s parent is bwc_complex.c, and it\nnever sets a default miss on its tables. The caller\u0027s best-effort detach\nonly hides the missing cleanup in the common case.\n\n5. Why 9fc43b5e3933a and not 71a1372b8275d\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAt 71a1372b8275d, at v6.12 and at v6.13, git grep for\nmlx5hws_table_create(), mlx5hws_table_destroy() and\nmlx5hws_table_set_default_miss() finds only the prototypes in mlx5hws.h\nand the definitions in mlx5hws_table.c / table.c. No code ever creates a\ntable, sets a default miss or destroys one, so the dangling node cannot\nbe created. The same holds for v6.12.111: the fix was backported to\n6.12.y as 16b81428f7b5f, but that branch still has no caller.\n\n0f3ecf5c57d86 (v6.14-rc1) adds the first callers, in fs_hws.c. They are\nreachable only through the struct mlx5_flow_cmds that\nmlx5_fs_cmd_get_hws_cmds() returns. At 0f3ecf5c57d86, and up to\n9fc43b5e3933a\u0027s parent, nothing calls mlx5_fs_cmd_get_hws_cmds(), and\nMLX5_FLOW_STEERING_MODE_HMFS is never assigned to anything: the devlink\nflow_steering_mode parameter accepts only \"dmfs\" and \"smfs\". So the\nfs_hws callers are dead code there too.\n\n9fc43b5e3933a is the commit that connects them:\n\n - mlx5_fs_mode_validate() accepts \"hmfs\" when\n   mlx5_fs_hws_is_supported() (new in this commit, a wrapper around\n   mlx5hws_is_supported()) is true and eswitch offloads are not yet\n   enabled.\n - mlx5_fs_mode_set() stores MLX5_FLOW_STEERING_MODE_HMFS in\n   dev-\u003epriv.steering-\u003emode.\n - mlx5_flow_namespace_set_mode() returns mlx5_fs_cmd_get_hws_cmds()\n   for that mode.\n - When the eswitch moves to switchdev,\n   esw_create_offloads_fdb_tables() calls\n   mlx5_flow_namespace_set_mode(root_ns, esw-\u003edev-\u003epriv.steering-\u003emode)\n   on the FDB root namespace. From then on, FDB flow tables are created,\n   chained (next_ft -\u003e mlx5_fs_set_ft_default_miss()) and destroyed\n   through the fs_hws code in section 4.\n\nAll three candidates first ship in v6.14-rc1.\nscripts/dyad --sha1 60e9e82f162ab gives:\n\n - --vulnerable 71a1372b8275d: 6.12 -\u003e 7.2, which claims v6.12 and\n   v6.13 are affected even though they have no caller;\n - --vulnerable 0f3ecf5c57d86: 6.14 -\u003e 7.2;\n - --vulnerable 9fc43b5e3933a: 6.14 -\u003e 7.2.\n\n9fc43b5e3933a is recorded because it is the first commit where the\nfailure can actually happen. The affected-version floor is the same as\nwith 0f3ecf5c57d86.\n\n6. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Vulnerable from: 9fc43b5e3933a (\"net/mlx5: fs, add HWS to steering\n   mode options\"), v6.14-rc1. It makes the HWS flow commands selectable\n   through devlink flow_steering_mode\u003dhmfs, which makes the incomplete\n   destroy reachable.\n - Defect origin: 71a1372b8275d (\"net/mlx5: HWS, added tables\n   handling\"), v6.12-rc1. It created the default_miss.next /\n   default_miss.head link between tables, but mlx5hws_table_destroy()\n   only guarded the destination side and freed a source table while it\n   was still linked. It has no callers in v6.12 or v6.13.\n - First in-tree caller: 0f3ecf5c57d86 (v6.14-rc1). Still unreachable\n   at that commit, and not the root cause.\n - Not responsible: ad4da6cc36ace (v6.14-rc1). It changed how the list\n   is maintained but left the destroy-time leak untouched.\n - Fixed by: 60e9e82f162ab (v7.2-rc1), which unlinks\n   tbl-\u003edefault_miss.next in mlx5hws_table_destroy() when miss_tbl is\n   set.\n - Affected range: kernels from v6.14 that have HWS built\n(CONFIG_MLX5_HW_STEERING) and run the FDB in hmfs steering mode, up to\nthe fix in v7.2-rc1. v6.12 and v6.13 contain the defective code but\ncannot reach it.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "33ca76395a53ef30d241aadc90998eb47a9e8d1c",
      "tree": "bc96f583912a24200d185b75b5efd1717bfaecc3",
      "parents": [
        "017aecbeededa61ce6f3a85046096014b6abd448"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:57:22 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98894: Add .vulnerable file\n\nVulnerable from: 4058e7cbfd0fb (\"drm/amdgpu: Commit CPER entry\"), by\nXiang Liu, 2025-02-12, first released in v6.15-rc1. That is the first\ncommit that calls amdgpu_cper_ring_write(), so it is where the hang\nfirst becomes reachable.\n\nThe defective code comes from a6d9d192903ea (\"drm/amdgpu: add data\nwrite function for CPER ring\"), by Tao Zhou, 2025-01-22, also first\nreleased in v6.15-rc1. At that commit amdgpu_cper_ring_write() has no\ncallers, so it is the defect origin rather than the vulnerable boundary\n(see section 2).\n\nThe loop that spins is the overflow rptr recovery loop in\namdgpu_cper_ring_write(), together with the entry-size parser\namdgpu_cper_ring_get_ent_sz() it relies on. git blame on the fix\u0027s\nparent shows that every line of that loop (amdgpu_cper.c:520-535) still\ndates from a6d9d192903ea, and nothing between it and the fix changed the\nloop. The fix, 4d695e66d7027 (\"drm/amd/ras: reset CPER ring on corrupt\nentry size\"), landed in v7.2-rc1 and has no Fixes: tag. The tag it\nshould carry names the code origin:\n\n\tFixes: a6d9d192903e (\"drm/amdgpu: add data write function for CPER ring\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe CPER ring holds RAS error records in a circular buffer. When a new\nrecord overwrites the oldest data (\"overflow\"), the writer must move\nrptr forward until it points at a valid \"CPER\" header again. Before the\nfix, the code was:\n\n\tpos \u003d (ring-\u003ewptr + 1) \u0026 ring-\u003eptr_mask;\n\tdo {\n\t\tent_sz \u003d amdgpu_cper_ring_get_ent_sz(ring, pos);\n\t\trptr +\u003d (ent_sz \u003e\u003e 2);\n\t\trptr \u0026\u003d ring-\u003eptr_mask;\n\t\t*ring-\u003erptr_cpu_addr \u003d rptr;\n\t\tpos \u003d rptr;\n\t} while (!amdgpu_cper_is_hdr(ring, rptr));\n\nThe fix computes next_rptr, ignores sizes below 4 bytes, and checks\nwhether next_rptr \u003d\u003d rptr. If the pointer did not move, it resets the\nring to empty (rptr \u003d wptr, count_dw \u003d (ring_size - 4) \u003e\u003e 2) and jumps\nto out_unlock.\n\n2. Why 4058e7cbfd0fb and not a6d9d192903ea\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\na6d9d192903ea only adds amdgpu_cper_ring_write() and\namdgpu_cper_ring_get_ent_sz(), plus the prototype in amdgpu_cper.h. At\nthat commit, git grep finds no caller of amdgpu_cper_ring_write() in the\ntree, so nothing can reach the loop.\n\ngit log -S\u0027amdgpu_cper_ring_write(\u0027 over a6d9d192903ea^..4d695e66d7027\nlists the commits that add callers, in order:\n\n - 4058e7cbfd0fb (\"drm/amdgpu: Commit CPER entry\"): replaces the two\n\"/*TODO: commit the cper entry to cper ring */\" stubs in\namdgpu_cper_generate_ue_record() and amdgpu_cper_generate_ce_records()\nwith amdgpu_cper_ring_write() calls.\n - f9d35b945c599 (\"drm/amdgpu: Generate bad page threshold cper\n   records\").\n - a91d91b600479 (\"drm/amdgpu: Add support for CPERs on\n   virtualization\").\n\nAt 4058e7cbfd0fb the path is live on real hardware:\n\n - amdgpu_device_ip_init() calls amdgpu_cper_init() when\n   amdgpu_aca_is_enabled(adev). The AMDGPU_RING_TYPE_CPER branch of\n   amdgpu_ring_init() sets up the ring with count_dw \u003d (ring_size - 4)\n   \u003e\u003e 2 and wptr \u003d rptr \u003d 0.\n - aca_banks_update() calls aca_banks_generate_cper() after dispatching\n   any valid ACA banks. That calls the UE/CE generators, which now call\n   amdgpu_cper_ring_write().\n - 8652920d2c002 (\"drm/amdgpu: add mutex lock for cper ring\") landed\n   before 4058e7cbfd0fb, so the loop already runs under\n   adev-\u003ecper.ring_lock at this commit.\n\nThe hang is reachable at 4058e7cbfd0fb even with well-formed,\nkernel-generated records. At that commit the count_dw update at the end\nof amdgpu_cper_ring_write() is\n\n\tif (ring-\u003ecount_dw \u003e\u003d (count \u003e\u003e 2))\n\t\tring-\u003ecount_dw -\u003d (count \u003e\u003e 2);\n\nbut count has already been reduced to 0 by the copy loop, so count_dw\nnever goes down from its initial (ring_size - 4) \u003e\u003e 2. With count_dw\nstuck at a non-zero value, amdgpu_cper_ring_get_ent_sz() always takes\nthe \"ring buffer is not full\" branch for non-header positions and\nreturns the distance to the end of the ring. Once an overflow leaves\nring[0] holding the tail of a wrapped record instead of a header, and\nthe first step from pos \u003d wptr + 1 does not happen to put rptr on a\nheader, the loop walks rptr to the end of the ring, it wraps to 0, and\nfrom there every step is the full ring size. That is the zero-step case\n2 below. 4d695e66d7027\u0027s next_rptr \u003d\u003d rptr check catches this case as\nwell and resets the ring.\n\nd6f9bbce18762 (\"drm/amdgpu: Fix computation for remain size of CPER\nring\") fixed the count_dw accounting later in the same merge window.\nIts message reads \"will cause unbreakable while cycle when CPER ring\noverflow\". That is this same rptr loop, triggered by stale count_dw.\nd6f9bbce18762 removed that trigger but left the loop unbounded. The\ncorrupt-data triggers described below still reach it.\n\nThe affected-version floor is the same either way.\na6d9d192903ea, 4058e7cbfd0fb, d6f9bbce18762 and a91d91b600479 all first\nship in v6.15-rc1, and scripts/dyad --sha1 4d695e66d7027 reports\n6.15 -\u003e 7.2 for --vulnerable set to any of them.\n\n3. How a6d9d192903ea\u0027s loop hangs\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe only way out of the loop is finding a \"CPER\" signature at rptr.\nNothing bounds the number of iterations or checks that rptr moved. The\nstep size comes from amdgpu_cper_ring_get_ent_sz(), added in the same\ncommit, and that value is read straight from ring contents:\n\n\tchunk \u003d ring-\u003ering_size - (pos \u003c\u003c 2);\n\tif (header at pos)        rec_len \u003d chdr-\u003erecord_length;   // untrusted\n\telse if (ring-\u003ecount_dw)  rec_len \u003d 0;\n\telse                      scan pos+1..buf_mask for \"CPER\"; rec_len \u003d distance\n\tcalc:\n\tif (!rec_len) return chunk;\n\telse          return umin(rec_len, chunk);\n\nThere are two ways the step can be zero, matching the fix\u0027s \"after\ndword conversion and pointer masking\":\n\n 1. Dword conversion: a corrupt header with a record_length of 1-3\n    gives ent_sz \u003e\u003e 2 \u003d\u003d 0.\n 2. Pointer masking: if rec_len \u003d\u003d 0 at pos \u003d\u003d 0, the function returns\n    the whole ring size. The CPER ring sets support_64bit_ptrs \u003d false,\n    so ptr_mask \u003d\u003d buf_mask \u003d\u003d ring_dw - 1, and\n    (0 + ring_dw) \u0026 ptr_mask \u003d\u003d 0. The pointer lands back where it\n    started.\n\nFrom the second iteration on, pos \u003d\u003d rptr, and the loop only continues\nwhen rptr is *not* a header. So get_ent_sz() always takes the non-header\npath:\n\n - If a \"CPER\" signature exists later in the ring, rptr jumps to it and\n   the loop exits.\n - Otherwise rptr moves to the end of the ring, which wraps to 0.\n - Once rptr \u003d\u003d 0, if ring[0] is not a header and either count_dw !\u003d 0\n   or no signature exists in dwords 1..buf_mask, the step is the full\n   ring size. rptr stays at 0 forever.\n\nOnce count_dw is accounted correctly (from d6f9bbce18762 on),\nwell-formed records always leave a signature somewhere, so normal\noperation exits. Corrupt contents (records without a \"CPER\" signature,\nor fake headers with bogus record_length values) do not. Corrupt data is\nreachable in practice through the SR-IOV guest path,\namdgpu_virt_write_cpers_to_ring() (added by a91d91b600479). It copies\nCPER blobs supplied by the host straight into this ring and never\nchecks the signature. Until ebbd73d089bd4 (a related fix from the same\nday) it did not check record_length either.\n\n4. Checking it in a model\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe pre-fix and post-fix code was modelled in a small Python simulation\n(64-dword ring, same arithmetic):\n\n\tScenario                            Pre-fix                With fix\n\t----------------------------------  ---------------------  ----------\n\tValid records, many wraps           ok                     ok\n\tRecords with no \"CPER\" signature    HANG, rptr stuck at 0  ring reset\n\tA record with \"CPER\" but            HANG, rptr stuck at 0  ring reset\n\t  record_length \u003d 2, then garbage\n\nA second model of the 4058e7cbfd0fb code fed it only well-formed\nrecords, again with a 64-dword ring:\n\n\tRecord sizes (bytes)  At 4058e7cbfd0fb                  With d6f9bbce18762\n\t--------------------  --------------------------------  ------------------\n\t40                    HANG on write 7, rptr\u003d0, cnt\u003d63   ok\n\t40, 56, 72            HANG on write 5, rptr\u003d0, cnt\u003d63   ok\n\t24, 200               HANG on write 4, rptr\u003d0, cnt\u003d63   ok\n\t128                   ok (records tile ring, ring[0]    ok\n\t                      is always a header)\n\n5. Why it hangs the worker and not just itself\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n8652920d2c002 (\"drm/amdgpu: add mutex lock for cper ring\", also\nv6.15-rc1) wrapped the write in adev-\u003ecper.ring_lock.\n8e0d1edb5c167 (\"drm/amd/amdgpu: fix missing lock for\ncper.ring-\u003erptr/wptr access\", v6.17-rc1) then moved mutex_lock() up to\nalso cover the rptr read. So the endless loop runs while holding that\nmutex. That blocks every other CPER writer (fatal, bad-page-threshold\nand corrected-error records, and the VF path) and the debugfs ring\nreader, which also takes the lock. This makes the impact worse, but the\nunbounded loop itself already existed in a6d9d192903ea.\n\n6. Commits that are not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 4d614ce8ffd75 (\"drm/amdgpu: add RAS CPER ring buffer\"): sets up the\n   ring and its non-64-bit ptr_mask, but has no read/write logic.\n - d6f9bbce18762 (\"drm/amdgpu: Fix computation for remain size of CPER\n   ring\"): fixed the count_dw accounting. That removed one trigger of\n   this loop (see section 2) without bounding the loop itself.\n - b8939bd764c9c (\"drm/amdgpu: fix CPER ring header parsing\",\n   v7.1-rc1): fixed out-of-bounds reads when a header wraps across the\n   ring end. It made parsing memory-safe but still trusts record_length,\n   and forward progress is unchanged.\n - a91d91b600479 (\"drm/amdgpu: Add support for CPERs on\n   virtualization\") / ebbd73d089bd4 (\"drm/amdgpu: bound SR-IOV RAS CPER\n   dump parsing against used_size\"): VF CPER support and bounds on\n   host-provided dumps. One is a source of corrupt data, the other a\n   partial input check. Neither touches the loop. a91d91b600479 is not\n   the boundary either, because the hang was already reachable at\n   4058e7cbfd0fb without any host-supplied data.\n\n7. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nKernels from v6.15-rc1 (4058e7cbfd0fb, the first caller of the loop\na6d9d192903ea added) up to the fix in v7.2-rc1 (4d695e66d7027).\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "017aecbeededa61ce6f3a85046096014b6abd448",
      "tree": "c5217cf095949e26e599a73b95d8a40667490ea6",
      "parents": [
        "2bb2eaee0338674d2c11ffb933a63ec698130988"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:45:02 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98893: Add .vulnerable file\n\nVulnerable from: e3811190acf85 (\"firmware: arm_scmi: Add SCMI v3.0\nsensor notifications\"), by Cristian Marussi, first released in\nv5.11-rc1.\n\nThat commit added the SCMI_EVENT_SENSOR_UPDATE case without any check\non the payload size. Every later change to that code was a refactor that\nleft the missing check alone. The fix, 32bc5496b4817, landed in v7.2-rc1\nand has no Fixes: tag. The tag it should carry:\n\n\tFixes: e3811190acf8 (\"firmware: arm_scmi: Add SCMI v3.0 sensor notifications\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 32bc5496b4817 (Sudeep Holla, v7.2-rc1, patch 3 of the\nscmi_fixes-v1 series, no Fixes: tag) changes only the\nSCMI_EVENT_SENSOR_UPDATE case of scmi_sensor_fill_custom_report() in\ndrivers/firmware/arm_scmi/sensors.c. It adds two checks:\n\n 1. \"if (payld_sz \u003c sizeof(*p)) break;\" makes sure the fixed 8-byte\n    header (agent_id, sensor_id) is actually present before it is\n    read.\n 2. After \"readings_count \u003d s-\u003enum_axis ?: 1\", it computes\n\n\texpected_sz \u003d sizeof(*p) + readings_count * sizeof(p-\u003ereadings[0])\n\n    and rejects payloads shorter than that. Each reading is a 16-byte\n    struct scmi_sensor_reading_resp.\n\nIt also deletes the comment \"/* payld_sz is variable for this event */\".\nThat comment was the only thing that had stood in for a size check.\n\n2. How the bug works\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nDelivery path:\n\n 1. The transport fills xfer-\u003erx.len from the length the platform\n    reports. For example, shmem_fetch_notification() uses\n    min(max_len, shmem-\u003elength - 4).\n 2. scmi_notify() checks only that the message is not too big:\n    len \u003e r_evt-\u003eevt-\u003emax_payld_sz. For SENSOR_UPDATE, max_payld_sz is\n    8 + 63*16 \u003d 1016. A short payload passes this check.\n 3. The header and payload are queued on the per-protocol kfifo.\n 4. The worker, scmi_process_event_payload(), copies exactly payld_sz\n    bytes into the per-protocol scratch buffer pd-\u003eeh-\u003epayld. It then\n    calls fill_custom_report(..., pd-\u003eeh-\u003epayld, pd-\u003eeh-\u003epayld_sz, ...).\n\nThe core passes the real payld_sz and leaves validation to each\nprotocol. That is by design, and the sensor trip-point case does its own\ncheck.\n\nThe parser does not check the length. In the SENSOR_UPDATE case:\n\n - It reads p-\u003esensor_id and p-\u003eagent_id with no check that payld_sz \u003e\u003d\n   8.\n - It sets readings_count \u003d s-\u003enum_axis ?: 1. num_axis comes from the\n   earlier SENSOR_DESCRIPTION_GET (clamped to 63), not from the\n   notification.\n - It loops readings_count times, calling scmi_parse_sensor_readings()\n   on p-\u003ereadings[i], and never compares 8 + readings_count*16 with\n   payld_sz.\n\nExample: a sensor reports 3 axes, so a correct notification needs 56\nbytes. If the firmware sends only 8 (or 0) bytes, the parser still\nbuilds three readings from bytes after the end of the real payload.\nFor a 0-byte payload, even sensor_id is taken from leftover bytes.\n\nReal-world effect:\n\n - The read never goes past the allocation. pd-\u003eeh is allocated as the\n24-byte struct scmi_event_header (payload starts at offset 17) plus the\nlargest max_payld_sz (1016), and num_axis is capped at 63. So there is\nno slab out-of-bounds access.\n - What gets parsed instead is stale data: bytes left in the shared\n   scratch buffer by earlier sensor-protocol notifications, or zeros\n   from devm_kzalloc() on first use.\n - That stale data is delivered as a valid struct\n   scmi_sensor_update_report to notifier callbacks.\nscmi_iio_sensor_update_cb() copies readings[i].value into iio_buf and\npushes it to userspace through the IIO buffer, so user space receives\nmade-up sensor values and timestamps.\n - A short payload whose leftover sensor_id bytes happen to be a valid\n   ID can also attribute the event to the wrong sensor.\n\n3. Tracing the history\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on scmi_sensor_fill_custom_report() in origin/master returns\nexactly these commits:\n\n - 128e3e9311a95 (\"firmware: arm_scmi: Add sensor notifications\n   support\"), v5.9-rc1. Created the function. It handled only\nSENSOR_TRIP_POINT_EVENT and returned NULL on \"evt_id !\u003d TRIP_POINT ||\nsizeof(*p) !\u003d payld_sz\". That check is strict and correct, so it is not\nthe root cause.\n - 72a5eb9d9c319 (\"firmware: arm_scmi: Remove fixed size fields from\n   reports/scmi_event_header\"), v5.10 cycle. Changed u64 timestamp to\n   ktime_t. The SENSOR_UPDATE case did not exist yet.\n - e3811190acf85 (\"firmware: arm_scmi: Add SCMI v3.0 sensor\n   notifications\"), v5.11-rc1. Introduced the bug (details below).\n - b9f7fd907c38a (\"firmware: arm_scmi: Convert events registration to\n   protocol handles\"), v5.13. Signature changed to \"const void *handle\";\n   sinfo fetched through a cast. No logic change.\n - 9694a7f623593 (\"firmware: arm_scmi: Port sensor protocol to new\n   protocols interface\"), v5.13. Switched sensor_priv to\n   ph-\u003eget_priv(ph). No logic change.\n - 3cb8c95f4b305 (\"firmware: arm_scmi: Cleanup events registration\n   transient code\"), v5.13. Signature changed to \"const struct\n   scmi_protocol_handle *ph\". No logic change.\n - 32bc5496b4817 (the fix), v7.2-rc1. Adds the checks.\n\nSeparately, e945927dc7c2d (v5.11-rc1) only renamed\nscmi_sensor_reading_le to scmi_sensor_reading_resp; the layout stayed\n4 x __le32.\n\n4. How e3811190acf85 introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThat commit added SCMI v3.0 SENSOR_UPDATE (continuous update) support:\n\n - It added the variable-length payload struct\n   scmi_sensor_update_notify_payld { agent_id; sensor_id; readings[]; }.\n - It turned scmi_sensor_fill_custom_report() from a single trip-point\n   handler into a switch (evt_id).\n - It added the sensor_events[] entry for SCMI_EVENT_SENSOR_UPDATE,\n   sized for 63 readings.\n\nWhen restructuring, it kept the trip-point case\u0027s\n\"if (sizeof(*p) !\u003d payld_sz) break;\". For the new case, which has a\nvariable-length array, it wrote only:\n\n\t/* payld_sz is variable for this event */\n\tr-\u003esensor_id \u003d le32_to_cpu(p-\u003esensor_id);\n\t...\n\tr-\u003ereadings_count \u003d s-\u003enum_axis ?: 1;\n\tfor (i \u003d 0; i \u003c r-\u003ereadings_count; i++)\n\t\tscmi_parse_sensor_readings(\u0026r-\u003ereadings[i], \u0026p-\u003ereadings[i]);\n\nIt recognised that an exact-size check could not work for a\nvariable-length payload, but put nothing in its place: no minimum-size\ncheck and no check tying readings_count to payld_sz. This is the code\nthe fix patches; the comment the fix deletes is the one this commit\nadded.\n\nThe same commit capped num_axis at 63 (in the descriptor code) and\nsized max_payld_sz for 63 readings. That is why the over-read stays\ninside the scratch buffer and returns stale bytes instead of overrunning\nthe slab.\n\nThe sibling fix 56e7e64cdd0e7 (CVE-2026-98892) applies the same kind of\nfix to the base protocol\u0027s error event. That bug came from a different\ncommit, 585dfab3fb80e. Only e3811190acf85 touches the sensor\nSENSOR_UPDATE path.\n\n5. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nEvery kernel from v5.11-rc1 up to the fix in v7.2-rc1 is affected,\nprovided:\n\n - an SCMI v3.0+ sensor platform is present,\n - SENSOR_UPDATE notifications are enabled (for example by scmi_iio),\n   and\n - the platform (firmware, or a VMM backend for virtio-scmi) sends a\n   SENSOR_UPDATE message shorter than 8 + num_axis*16 bytes.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "2bb2eaee0338674d2c11ffb933a63ec698130988",
      "tree": "4f0e11e8f477c93cc0f56fa14a3a839f3fe7f5b4",
      "parents": [
        "acc1c04a790d4d6cf223611096cfcb3057c42754"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:39:56 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:15 2026 -0400"
      },
      "message": "CVE-2026-98892: Add .vulnerable file\n\nVulnerable from: 585dfab3fb80 (\"firmware: arm_scmi: Add base\nnotifications support\"), by Cristian Marussi, first released in\nv5.9-rc1.\n\nThat commit created the BASE_ERROR_EVENT parser with this defect, and\nevery later change to the parser only changed its signature. The fix,\n56e7e64cdd0e, landed in v7.2-rc1 and has no Fixes: tag. The tag it\nshould carry:\n\n\tFixes: 585dfab3fb80 (\"firmware: arm_scmi: Add base notifications support\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix is in scmi_base_fill_custom_report() in\ndrivers/firmware/arm_scmi/base.c. That function turns a raw\nBASE_ERROR_EVENT payload into a struct scmi_base_error_report. The\npayload layout is:\n\n\tstruct scmi_base_error_notify_payld {\n\t\t__le32 agent_id;\n\t\t__le32 error_status;\n\t#define IS_FATAL_ERROR(x)\t((x) \u0026 BIT(31))\n\t#define ERROR_CMD_COUNT(x)\tFIELD_GET(GENMASK(9, 0), (x))\n\t\t__le64 msg_reports[SCMI_BASE_MAX_CMD_ERR_COUNT];   /* 1024 */\n\t};\n\nThis is the only variable-length SCMI notification. The number of valid\nmsg_reports[] entries comes from bits [9:0] of error_status, which is a\nvalue the platform firmware supplies (0..1023). Before the fix, the only\nsize check was:\n\n\tif (evt_id !\u003d SCMI_EVENT_BASE_ERROR_EVENT || sizeof(*p) \u003c payld_sz)\n\t\treturn NULL;\n\nThat check only rejects payloads that are too large. Nothing rejected\npayloads that were too small:\n\n - Missing header check: a payload shorter than 8 bytes still had\n   agent_id and error_status read from it.\n - Missing array check: cmd_count \u003d ERROR_CMD_COUNT(error_status) was\n   trusted as-is. The loop\n\n\tfor (i \u003d 0; i \u003c r-\u003ecmd_count; i++)\n\t\tr-\u003ereports[i] \u003d le64_to_cpu(p-\u003emsg_reports[i]);\n\n   read entries that were never received.\n\nThe fix adds two checks: payld_sz \u003e\u003d offsetof(msg_reports), and\npayld_sz \u003e\u003d offsetof(msg_reports) + cmd_count * 8. Either failure\nreturns NULL.\n\n2. How the bad data is reached\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Interrupt side. The transport copies the notification into\n    xfer-\u003erx.buf, and xfer-\u003erx.len is the length the platform\n    reported, capped at max_msg_size. scmi_handle_notification()\n    passes both to scmi_notify().\n 2. scmi_notify() (notify.c). It only rejects\n    len \u003e r_evt-\u003eevt-\u003emax_payld_sz. It then queues a header (with\n    payld_sz \u003d len) and exactly len payload bytes into the protocol\u0027s\n    kfifo.\n 3. Worker side.\n     - scmi_process_event_header() reads the header into pd-\u003eeh.\n     - scmi_process_event_payload() reads exactly payld_sz bytes into\n       pd-\u003eeh-\u003epayld.\n     - REVT_FILL_REPORT() then calls\n       scmi_base_fill_custom_report(ph, evt_id, ts, pd-\u003eeh-\u003epayld,\n       pd-\u003eeh-\u003epayld_sz, r_evt-\u003ereport, \u0026src_id).\n     - This parsing runs before any handler lookup.\n 4. The parser. It reads cmd_count and loops over msg_reports[] up to\n    that count, without comparing it to payld_sz.\n\npd-\u003eeh is a scratch buffer allocated once per protocol with\ndevm_kzalloc(), sized as the header plus the largest max_payld_sz of\nthat protocol\u0027s events. For the base protocol that is 8 + 1024*8 bytes.\nEach event only overwrites the first payld_sz bytes. So anything the\nparser reads past payld_sz is either:\n\n - leftover bytes from an earlier BASE_ERROR_EVENT on the same protocol\n   queue, or\n - zeroes from the original allocation.\n\nReal transports make this mismatch easy to hit:\n\n - Size caps: shared memory (SCMI_SHMEM_MAX_PAYLOAD_SIZE \u003d 104) and\n   virtio (VIRTIO_SCMI_MAX_MSG_SIZE \u003d 128) can carry only about 12\n   reports.\n - Unchecked count: error_status can still claim up to 1023.\n\nA firmware that is buggy, truncated or malicious can therefore get a\nreport built from data it never sent: wrong agent_id/fatal/cmd_count\nwhen the payload is under 8 bytes, and stale or zero reports[i] entries\nbeyond what was received.\n\nImpact: this is not a heap overflow, and should not be overstated.\n\n - ERROR_CMD_COUNT is 10 bits, so it is at most 1023.\n - The scratch buffer holds 1024 entries.\n - The report buffer (max_report_sz \u003d sizeof(report) +\n   1024*sizeof(u64)) also holds 1024 entries.\n\nSo every read and write stays inside its own allocation. The defect is\nthat unvalidated and stale data reaches notification consumers. No\nin-tree code subscribes to SCMI_EVENT_BASE_ERROR_EVENT.\n\n3. Why 585dfab3fb80 is the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n585dfab3fb80 (part of the series that added SCMI notifications,\nalongside bd31b249692e, e27077bc04d5, fb5086dc4746, 128e3e9311a9 and\n469ca1822d64) added, in one go:\n\n - the payload struct with msg_reports[1024] and the ERROR_CMD_COUNT()\n   macro;\n - scmi_base_fill_custom_report() with only the oversize check, the\n   trusted cmd_count, and the unbounded loop;\n - the base_events[] entry (max_payld_sz \u003d sizeof(struct\n   scmi_base_error_notify_payld)), which makes the core accept any\n   length up to the maximum;\n - registration of the base protocol with the notification core.\n\nIts own comment states the assumption it then fails to enforce:\n\n\t\"BaseError notification payload is variable in size ... payld_sz\n\t is the effective length of this notification payload\"\n\nThe sibling protocols from the same series do this correctly. Power,\nperf, sensors and reset all use the exact check sizeof(*p) !\u003d payld_sz.\nBase is the one variable-length event, and the only one that never\nchecked its declared count against the length it received.\n\n4. Commits that are not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Later changes to the parser are signature-only. git log -L over\n   scmi_base_fill_custom_report() up to the fix shows only:\n\n    - 72a5eb9d9c31 (timestamp type u64 -\u003e ktime_t);\n    - b9f7fd907c38, 8d3581c2526f and 3cb8c95f4b30 (the handle\n      parameter becoming a protocol handle ph).\n\nThe size check and the loop are byte-for-byte what 585dfab3fb80 wrote.\nThe payload struct and the base_events[] table have never changed since\nthen.\n\n - bd31b249692e (notification dispatch core). It deliberately passes\n   the effective payld_sz and leaves validation to each protocol\u0027s\n   callback, as the sibling protocols show. Its scratch buffer sized to\n   the maximum is the reason this is not a memory-safety bug.\n\nAffected range: v5.9-rc1 through v7.2-rc1 (exclusive).\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "acc1c04a790d4d6cf223611096cfcb3057c42754",
      "tree": "c6ce48454373dd79a7723c90b4f9ede50f1220bc",
      "parents": [
        "1782af65407108df03690afc6b674852b32a3205"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:34:44 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98891: Add .vulnerable file\n\nVulnerable from: c9c99f8938e4 (\"mac80211: act upon and report deauth\nwhile associating\"), by Johannes Berg, 2015, merged in v4.2-rc1.\n\nThat commit moved the reason-code read out of the function body and\ninto the variable\u0027s declaration. The declaration sits above the len \u003c 24\n+ 2 check, so the read now happens before the length is validated. The\nbug stayed in that form until the fix, 4a360c6e18df\n(\"wifi: mac80211: validate deauth frame length before reason access\"),\nwhich landed in v7.2-rc4 without a Fixes: tag.\n\nSuggested tag:\n\n\tFixes: c9c99f8938e4 (\"mac80211: act upon and report deauth while associating\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore the fix, ieee80211_rx_mgmt_deauth() in net/mac80211/mlme.c\nlooked like this:\n\n\tstruct ieee80211_if_managed *ifmgd \u003d \u0026sdata-\u003eu.mgd;\n\tu16 reason_code \u003d le16_to_cpu(mgmt-\u003eu.deauth.reason_code);   // read happens here\n\n\tlockdep_assert_wiphy(sdata-\u003elocal-\u003ehw.wiphy);\n\n\tif (len \u003c 24 + 2)                                             // checked only here\n\t        return;\n\nThe fix turns reason_code into a plain declaration and assigns it only\nafter the check. It also spells the bound as offsetofend(struct\nieee80211_mgmt, u.deauth.reason_code). In struct ieee80211_mgmt the\n802.11 header takes 24 bytes (frame_control 2, duration 2, da/sa/bssid 6\neach, seq_ctrl 2), and u.deauth.reason_code occupies bytes 24-25. So\noffsetofend equals 26, the same as 24 + 2.\nThe bound is unchanged; the only real fix is the order of read and\ncheck.\n\n2. The commit that introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nc9c99f8938e4 changed the deauth handler so that it also handles a\ndeauth received while associating. Before it, the function returned\nearly unless the station was associated, and it read the reason code\nonly after both the length check and the BSSID check. Here is that\ncommit\u0027s change to the function:\n\n\t \tstruct ieee80211_if_managed *ifmgd \u003d \u0026sdata-\u003eu.mgd;\n\t-\tconst u8 *bssid \u003d NULL;\n\t-\tu16 reason_code;\n\t+\tu16 reason_code \u003d le16_to_cpu(mgmt-\u003eu.deauth.reason_code);\n\n\t \tsdata_assert_lock(sdata);\n\n\t \tif (len \u003c 24 + 2)\n\t \t\treturn;\n\n\t-\tif (!ifmgd-\u003eassociated ||\n\t-\t    !ether_addr_equal(mgmt-\u003ebssid, ifmgd-\u003eassociated-\u003ebssid))\n\t-\t\treturn;\n\t-\n\t-\tbssid \u003d ifmgd-\u003eassociated-\u003ebssid;\n\t-\n\t-\treason_code \u003d le16_to_cpu(mgmt-\u003eu.deauth.reason_code);\n\t+\tif (ifmgd-\u003eassociated \u0026\u0026 ether_addr_equal(...)) {   /* branch 1: associated */\n\t+\t\t... reason_code ...\n\t+\t}\n\t+\tif (ifmgd-\u003eassoc_data \u0026\u0026 ether_addr_equal(...)) {   /* branch 2: associating */\n\t+\t\t... reason_code ...\n\t+\t}\n\nThe function now had two branches that both used reason_code. To avoid\nreading it twice, the commit moved the read into the declaration\u0027s\ninitializer. C runs initializers at the point of declaration, so the\n2-byte load from offset 24 now ran before if (len \u003c 24 + 2). The check\nstayed where it was, but it now came after the access it was meant to\nprotect.\n\n3. Evidence that this is the original commit, not just a later carrier\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Pickaxe:\n\n\tgit log -S\u0027u16 reason_code \u003d le16_to_cpu(mgmt-\u003eu.deauth.reason_code);\u0027 -- net/mac80211/\n\nreturns exactly two commits: c9c99f8938e4, which adds the line, and\n4a360c6e18df, which removes it.\n\n - Every earlier version was safe. The handler was checked at each\n   point in its history, back to the original mac80211 import\n   f0706e828e96 (v2.6.22, then in ieee80211_sta.c). Each version reads\n   the reason code only after len \u003c 24 + 2. That covers 469002983fc9,\n77fdaa12cea2, 66e67e418908, 8d61ffa5e01c, 6ff57cf88807, dfa1ad299184\nand a90faa9d6449, the last version before the bug.\n\n - Later commits only carried the early read along. These all touched\n   the function but kept the initializer:\n     - 79c92ca42b5a (the TDLS branch, which uses reason_code before the\n       BSS checks)\n     - e6f462df9acd\n     - 3f8a39ff2807\n     - c8fe4b0b37f6\n     - bfd8403adddd\n     - afa2d65938fe\n     - 81151ce462e5\n     - 076fc8775daf\n\n4. How a frame reaches the bad read\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. In net/mac80211/rx.c, ieee80211_rx_handle_packet only requires a\n    management frame to be at least as long as its 24-byte header,\n    then calls skb_linearize().\n 2. ieee80211_rx_h_mgmt_check() drops frames shorter than 24 bytes and\n    has a minimum size for action frames, but none for deauth.\n 3. ieee80211_rx_h_mgmt() drops multicast deauths that aren\u0027t\n    broadcast and lets station/IBSS interfaces through. The frame is\n    then queued on sdata-\u003eskb_queue.\n 4. ieee80211_sta_rx_queued_frame() calls\n    ieee80211_rx_mgmt_deauth(sdata, mgmt, skb-\u003elen).\n\nSo a deauth frame of exactly 24 bytes (header only, from any\ntransmitter in radio range) arrives with len \u003d\u003d 24. The initializer then\nreads skb-\u003edata[24..25], which is past the end of the frame.\n\n5. Impact\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe practical impact is small:\n\n - The frame has been linearized, and skb_shared_info always follows\n   the data area. So those 2 bytes are tailroom or shared-info bytes\n   inside the same skb head buffer. This is not a read outside the\n   allocated memory, and KASAN wouldn\u0027t report it.\n - On the short-frame path the function returns immediately, so the\n   value read is thrown away. It is never logged, passed to\n   ieee80211_tdls_handle_disconnect() or ieee80211_report_disconnect(),\n   or sent to userspace. Nothing leaks.\n\nIt is still a genuine read-before-validate bug, and c9c99f8938e4 is\nwhere it started.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "1782af65407108df03690afc6b674852b32a3205",
      "tree": "a16821b5340b1a2283c629afe42aaf7a8338221d",
      "parents": [
        "fde8aa10dd8b8caa585cdb6031a94052f250c2fa"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:30:24 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98890: Add .vulnerable file\n\nVulnerable from: 876c9d3aeb989 (\"[PATCH] Marvell Libertas 8388 802.11b/g\nUSB driver\"), by Marcelo Tosatti, February 2007, first released in\nv2.6.22-rc1.\n\nThat commit first added libertas to mainline. The unchecked\nmonitor/radiotap TX parsing was in it from the start, and nothing after\nit changed the logic.\n\nSuggested tag:\n\n\tFixes: 876c9d3aeb98 (\"[PATCH] Marvell Libertas 8388 802.11b/g USB driver\")\n\nThe fix 13ff543e0b2c7 (\"wifi: libertas: reject short monitor TX\nframes\", Pengpeng Hou) has no Fixes: tag.\n\n0. The fix has been reverted on master\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nOn origin/master the fix itself has been reverted, by 9727d1c4e1c97\n(\u0027Revert \"wifi: libertas: reject short monitor TX frames\"\u0027, Johannes\nBerg, 2026-10-07, Cc: stable, Reported-by: Takashi Iwai).\n\nThe new check runs after driver_lock has already been dropped and\ntx_pending_len set to -1:\n\n\tpriv-\u003etx_pending_len \u003d -1;\n\tspin_unlock_irqrestore(\u0026priv-\u003edriver_lock, flags);\n\t...\n\tif (priv-\u003ewdev-\u003eiftype \u003d\u003d NL80211_IFTYPE_MONITOR) {\n\t\t...\n\t\tif (skb-\u003elen \u003c sizeof(*rtap_hdr) + 4 + ETH_ALEN) {\n\t\t\t...\n\t\t\tgoto free;\n\t\t}\n\nIts goto free then falls through to unlock:, so it calls\nspin_unlock_irqrestore() on a lock that is already unlocked.\ntx_pending_len also stays at -1 with the queue stopped, so transmit\nhangs for good. So origin/master is currently vulnerable again.\n\n1. What the fix addresses\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIn lbs_hard_start_xmit() (drivers/net/wireless/marvell/libertas/tx.c),\nwhen the interface is in monitor mode:\n\n\tstruct tx_radiotap_hdr *rtap_hdr \u003d (void *)skb-\u003edata;\n\ttxpd-\u003etx_control \u003d cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr-\u003erate)); /* reads byte 8 */\n\tp802x_hdr +\u003d sizeof(*rtap_hdr);                        /* +12 */\n\tpkt_len  -\u003d sizeof(*rtap_hdr);                         /* uint16_t, wraps if len \u003c 12 */\n\tmemcpy(\u0026txpd-\u003etx_dest_addr, p802x_hdr + 4, ETH_ALEN);  /* reads bytes 16..21 */\n\t...\n\tmemcpy(\u0026txpd[1], p802x_hdr, pkt_len);                  /* into tx_pending_buf[2312] */\n\nThe function assumes the frame starts with a 12-byte struct\ntx_radiotap_hdr: the 8-byte fixed radiotap header plus rate, txpower,\nrts_retries and data_retries. It assumes an 802.11 header follows, with\nits addr1 field at offset 4. Reading that address needs\n12 + 4 + 6 \u003d 22 bytes.\n\nThe only length check was\n\n\t!skb-\u003elen || skb-\u003elen \u003e MRVDRV_ETH_TX_PACKET_BUFFER_SIZE\n\nwhich only rejects empty or oversized frames. The fix adds\n\n\tskb-\u003elen \u003c sizeof(*rtap_hdr) + 4 + ETH_ALEN\n\nHow it is reached\n-----------------\n\nlbs_set_monitor_mode() turns the netdev into ARPHRD_IEEE80211_RADIOTAP.\nThe device was created with ether_setup(), so hard_header_len and\nmin_header_len are both 14 (ETH_HLEN). A raw AF_PACKET socket (needs\nCAP_NET_RAW) therefore passes dev_validate_header() with any frame of\n14 bytes or more.\n\n - Frames of 14-21 bytes: the dest-address memcpy() reads up to 8 bytes\n   past the end of the skb data. Those bytes are copied into the txpd\n   sent to the firmware.\n\n - Frames under 12 bytes: not reachable through AF_PACKET today. If one\n   did arrive, the uint16_t pkt_len would wrap to about 65 KB, and the\n   final memcpy() would overflow priv-\u003etx_pending_buf[LBS_UPLD_SIZE \u003d\n   2312]. The added check closes that case too.\n\n2. How the code got here (git log --follow on tx.c)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 876c9d3aeb98, initial driver import (2007-02): Introduces it.\nSendSinglePacket(): if (radiomode \u003d\u003d WLAN_RADIOMODE_RADIOTAP) reads\npradiotap_hdr-\u003erate, skips sizeof(struct tx_radiotap_hdr), and subtracts\nit from tx_packet_length.\nif (linkmode \u003d\u003d WLAN_LINKMODE_802_11) memcpy(tx_dest_addr_high,\np802x_hdr + 4, ETH_ALEN). The only check is !skb-\u003elen || skb-\u003elen \u003e\nMRVDRV_ETH_TX_PACKET_BUFFER_SIZE.\n\n - 965f8bbc6c92 (\"monitor mode support for OLPC firmware\", 2007-08):\nOnly replaces the radiomode/linkmode tests with monitormode !\u003d\nWLAN_MONITOR_OFF.\n\n - 6b4a7e0fbd77 / a9bdce6564b2 / 8af23b2f96dc (2007-12): Move and merge\n   SendSinglePacket() into lbs_hard_start_xmit(). Parsing unchanged.\n\n - a2b62dc1f17d (\"clean up lbs_hard_start_xmit()\", 2007-12): Rewrites\n   the same logic into today\u0027s form (rtap_hdr, pkt_len -\u003d\n   sizeof(*rtap_hdr), memcpy(..., p802x_hdr + 4, ETH_ALEN)). Still no\n   minimum length.\n\n - a97bcfed96b5 (2007-12): The condition becomes dev \u003d\u003d\n   priv-\u003ertap_net_dev.\n\n - e86dc1ca4676 (\"Libertas: cfg80211 support\", 2010-06, v2.6.36): The\n   condition becomes priv-\u003ewdev-\u003eiftype \u003d\u003d NL80211_IFTYPE_MONITOR.\n\n - 5fd32ae0433a (Kees Cook, 2021-11): tx_dest_addr_high becomes the\n   tx_dest_addr struct_group. No object-code change.\n\n - 13ff543e0b2c (2026-07): The fix.\n\n - 9727d1c4e1c9 (2026-10): Reverts the fix (locking bug in section 0).\n\ngit blame on the pre-fix function agrees: the \"set txpd fields from the\nradiotap header\" and \"skip the radiotap header\" lines still come from\n876c9d3aeb98, and the rest come from the reshuffles above.\n\n3. Why 876c9d3aeb98 is the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe underlying defect is that the driver trusts a variable-length,\nuser-supplied frame to contain a fixed 12-byte radiotap header plus an\n802.11 header, and checks only for zero length. That assumption first\nappears in 876c9d3aeb98.\n\nMonitor mode could be switched on there through the private ioctls\nWLAN_SET_RADIOMODE and WLAN_SET_LINKMODE. So a short frame on the\ninterface could already be read out of bounds, and tx_packet_length\ncould already underflow.\n\nEvery later commit kept the same layout assumption and the same\ninsufficient check while renaming the mode test, moving the code, or\nconverting to cfg80211.\n\n4. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nv2.6.22-rc1 through the present. Since the fix is reverted on\norigin/master, a corrected fix is still needed. Either:\n\n - check the length before spin_lock_irqsave() / tx_pending_len \u003d -1, or\n\n - reset tx_pending_len and skip the second unlock on that error path.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "fde8aa10dd8b8caa585cdb6031a94052f250c2fa",
      "tree": "939f7d911e29654e71662c3d0361892d4477ec0a",
      "parents": [
        "da03ed53581362b9b11667193d7f02466300a43d"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:25:24 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98889: Add .vulnerable file\n\nVulnerable from: eae94cf82d745 (\"iwlwifi: mvm: add support for 6GHz\"),\nby Luca Coelho and seven co-developers, first released in v5.11-rc1.\n\nThat commit moved the size check in iwl_mvm_sched_scan_start() so it\nruns after iwl_mvm_build_scan_probe() has already copied the IEs into a\nfixed 512-byte buffer on the stack. Before it, the check always ran\nbefore the copy. As first written, it also skipped the check entirely\nunless the request had non-PSC 6 GHz channels.\n\nSuggested tag:\n\n\tFixes: eae94cf82d74 (\"iwlwifi: mvm: add support for 6GHz\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 4f155d262b31b (\"wifi: iwlwifi: mvm: fix sched scan IE sizing\",\nv7.2-rc5) has no Fixes: tag and changes two things:\n\n 1. scan.c: in iwl_mvm_sched_scan_start(),\n    iwl_mvm_build_scan_probe(mvm, vif, ies, \u0026params) moves from before\n    the 6 GHz PSC filtering and iwl_mvm_scan_fits() to after the fits\n    check. The probe is now built only once the IE lengths are known to\n    fit.\n\n 2. mac80211.c: max_sched_scan_ie_len changes from the hard-coded\n    SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2 to\n    iwl_mvm_max_scan_ie_len(mvm). The new value also subtracts the DS\n    Parameter Set (3 bytes) and WFA TPC (9 bytes) elements that the\n    driver inserts.\n\nChange 1 fixes the memory corruption. Change 2 only makes the limit\nreported to userspace accurate; section 5 explains why it isn\u0027t the root\ncause.\n\n2. How the overflow happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe destination buffer sits inside a struct on the stack:\n\n\tstruct iwl_mvm_scan_params {\n\t\t...\n\t\tint n_match_sets;\n\t\tstruct iwl_scan_probe_req preq;          /* ... u8 buf[SCAN_OFFLOAD_PROBE_REQ_SIZE]; 512 bytes */\n\t\tstruct cfg80211_match_set *match_sets;   /* right after buf */\n\t\tint n_scan_plans;\n\t\tstruct cfg80211_sched_scan_plan *scan_plans;\n\t\t...\n\t};\n\t...\n\tint iwl_mvm_sched_scan_start(...)\n\t{\n\t\tstruct iwl_mvm_scan_params params \u003d {};   /* on the stack */\n\niwl_mvm_build_scan_probe() copies into params-\u003epreq.buf with no bounds\nchecks. It writes, in order:\n\n - the 24-byte 802.11 header and a 2-byte empty SSID element;\n\n - ies-\u003eies[2GHZ], with a 3-byte DS Parameter Set element inserted if\n   RRM is supported;\n\n - ies-\u003eies[5GHZ];\n\n - ies-\u003eies[6GHZ];\n\n - ies-\u003ecommon_ies;\n\n - optionally the 9-byte WFA TPC element.\n\nEach copy is a plain memcpy(pos, ..., len). The function relies\nentirely on its caller having run iwl_mvm_scan_fits() first. That helper\ncompares the IE total against iwl_mvm_max_scan_ie_fw_cmd_room():\n512 - 24 - 2 - 9 - 3 on current trees.\n\nWhy the IE lengths can exceed the buffer\n----------------------------------------\n\nThe IEs reach the driver as follows:\n\n 1. NL80211_CMD_START_SCHED_SCAN (requires CAP_NET_ADMIN) only checks\n    ie_len \u003e wiphy-\u003emax_sched_scan_ie_len\n    (net/wireless/nl80211.c:11540). With the old limit, userspace could\n    supply up to 486 bytes.\n\n 2. mac80211\u0027s __ieee80211_request_sched_scan_start() then builds\n    per-band IEs with iebufsz \u003d local-\u003escan_ies_len + req-\u003eie_len.\n    These add supported/extended rates, HT, VHT, HE, EHT and HE 6 GHz\n    capabilities for each band, on top of the user IEs.\n\n 3. For normal scans, mac80211 subtracts scan_ies_len from\n    max_scan_ie_len (net/mac80211/main.c:1486-1487). It never does that\n    for max_sched_scan_ie_len.\n\nSo the combined lengths in struct ieee80211_scan_ies that reach the\ndriver can be well over 512. Only iwl_mvm_scan_fits() stands between\nthem and the 512-byte buffer.\n\nWhat the buggy ordering does\n----------------------------\n\nBefore the fix, the copy ran before the check:\n\n\tiwl_mvm_build_scan_probe(mvm, vif, ies, \u0026params);   /* memcpy into preq.buf[512] */\n\t/* PSC filtering ... */\n\tif (!iwl_mvm_scan_fits(mvm, req-\u003en_ssids, ies, params.n_channels)) {  /* too late */\n\t\tret \u003d -ENOBUFS;\n\t\tgoto out;\n\t}\n\nOversized, partly user-controlled IEs therefore overrun preq.buf. The\nwrite corrupts the match_sets, scan_plans and scan_6ghz_params pointers\nthat follow it, and can then go past the end of params into the rest of\nthe frame (saved registers, stack canary, return address). The -ENOBUFS\ncomes afterwards and undoes nothing.\n\n3. History: the check always came first until the 6 GHz commit\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on iwl_mvm_sched_scan_start() and its predecessors gives:\n\n - 35a000b7c1bbd (\"iwlwifi: mvm: support sched scan if supported by the\n   fw\"), David Spinadel, v3.13-rc1: Original sched scan. The probe was\n   built by iwl_mvm_fill_probe_req(..., left \u003d\nSCAN_OFFLOAD_PROBE_REQ_SIZE), which checks the remaining space before\nevery copy:\n\n\tif (WARN_ON(left \u003c ie_len))\n\t\treturn len;\n\n   Safe. This commit also introduced\n\n\tmax_sched_scan_ie_len \u003d SCAN_OFFLOAD_PROBE_REQ_SIZE - 24 - 2\n\n - 999d2568ee0c2 (\"iwlwifi: mvm: combine scan size checks into a common\n   function\"), v4.2-rc1: Added iwl_mvm_scan_fits(), called at the top of\n   iwl_mvm_sched_scan_lmac/umac() before anything was built. Safe.\n\n - 45d1b12e7c257 (\"iwlwifi: mvm: combine LMAC and UMAC preq\n   generation\"), v4.2-rc1: Added iwl_mvm_build_scan_probe() with no\n   bounds checks, called in iwl_mvm_sched_scan_start() after the early\n\n\tif (!iwl_mvm_scan_fits(...))\n\t\treturn -ENOBUFS;\n\n   Safe, because of the ordering.\n\n - About 20 later refactors (2015-2019): The early check stayed ahead\n   of iwl_mvm_build_scan_probe() throughout. Safe.\n\n - eae94cf82d745 (\"iwlwifi: mvm: add support for 6GHz\"), v5.11-rc1:\n   Introduces the bug (see section 4).\n\n - d44162280899c (\"wifi: iwlwifi: mvm: fix iwl_mvm_scan_fits()\n   calculation\"), v6.11-rc6: Partial fix. Made the check unconditional\n   and counted 6 GHz IEs, but left it after the copy.\n\n - 916a5d9c5354c (\"wifi: iwlwifi: mvm: fix\n   iwl_mvm_max_scan_ie_fw_cmd_room()\"), v6.11-rc6: Subtracted the TPC\n   element from the room calculation. Ordering unchanged.\n\n - 4f155d262b31b (this fix), v7.2-rc5: Moves the copy after the check.\n\n4. What eae94cf82d745 did\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nTo scan only PSC channels in 6 GHz, it needed to run the fits check on\nthe filtered channel count. So it deleted the early check and re-added\nit further down:\n\n\t \tif (WARN_ON(!mvm-\u003escan_cmd))\n\t \t\treturn -ENOMEM;\n\n\t-\tif (!iwl_mvm_scan_fits(mvm, req-\u003en_ssids, ies, req-\u003en_channels))\n\t-\t\treturn -ENOBUFS;\n\t ...\n\t \tiwl_mvm_build_scan_probe(mvm, vif, ies, \u0026params);     /* now runs unvalidated */\n\n\t+\t/* for 6 GHZ band only PSC channels need to be added */\n\t+\tfor (i \u003d 0; i \u003c params.n_channels; i++) { ... non_psc_included \u003d true; ... }\n\t+\tif (non_psc_included) { kmemdup + filter ... params.n_channels \u003d j; }\n\t+\n\t+\tif (non_psc_included \u0026\u0026\n\t+\t    !iwl_mvm_scan_fits(mvm, req-\u003en_ssids, ies, params.n_channels)) {\n\t+\t\tkfree(params.channels);\n\t+\t\treturn -ENOBUFS;\n\t+\t}\n\nThis introduced three problems at once:\n\n 1. Copy before check. The new check sits after\n    iwl_mvm_build_scan_probe(), so the unchecked memcpy()s into\n    params.preq.buf[512] run on unvalidated lengths. This is exactly\n    what 4f155d262b31b fixes.\n\n 2. The check was usually skipped. It ran only when non-PSC 6 GHz\n    channels were present. On any other request, including every\n    request on hardware without 6 GHz, there was no length check at\n    all. The overflow then left corrupted params.match_sets and\n    params.scan_plans pointers, which iwl_mvm_build_scan_cmd() then\n    used. d44162280899c fixed this part in 2024.\n\n 3. 6 GHz IEs weren\u0027t counted. The same commit added the 6 GHz copy to\n    iwl_mvm_build_scan_probe():\n\n\tmemcpy(pos, ies-\u003eies[NL80211_BAND_6GHZ], ...)\t/* band_data[2] */\n\n    but iwl_mvm_scan_fits() still summed only common, 2 GHz and 5 GHz\n    lengths. d44162280899c fixed this too.\n\nd44162280899c repaired problems 2 and 3 but not problem 1. So from\nv5.11 to v7.2-rc5, scheduled scans have built the probe before\nvalidating it. The normal scan path (_iwl_mvm_single_scan_start()) was\nnever changed this way: its check still runs before\niwl_mvm_build_scan_probe().\n\n5. Why the advertised limit is not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n35a000b7c1bbd (2013) set max_sched_scan_ie_len \u003d 512 - 24 - 2. It\nbecame stale when DS insertion (2014-2015) and TPC insertion\n(7f9565cbca1aa, 2017) were added. It isn\u0027t the root cause, for two\nreasons:\n\n - From 2013 until eae94cf82d745, an oversized request was always\n   caught before any write: first by fill_probe_req\u0027s \"left\" checks,\n   then by the early iwl_mvm_scan_fits(). A too-large advertised limit\n   only produced -ENOBUFS.\n\n - Even the corrected limit can\u0027t guarantee a fit. mac80211 adds its\n   own per-band IEs on top of the userspace IEs and never subtracts them\n   for sched scans. The only real protection is checking before copying,\n   and eae94cf82d745 removed that.\n\nThat second change in the fix makes the userspace limit accurate; it\ndoesn\u0027t fix the memory corruption.\n\n6. Exposure\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Affected trees: v5.11 and later, until 4f155d262b31b. Trees from\n   v5.11 up to v6.11-rc6 (without d44162280899c) are worse, since most\n   sched scans there had no length check at all.\n\n - Reachable from: NL80211_CMD_START_SCHED_SCAN (CAP_NET_ADMIN) through\n   iwl_mvm_mac_sched_scan_start() (mac80211.c:4181), and the WoWLAN\n   net-detect path in d3.c:1204.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "da03ed53581362b9b11667193d7f02466300a43d",
      "tree": "cec93ebd587560e98550554dece813bcdcb9565e",
      "parents": [
        "0b8002169036f664be3921d460a97f68c4045e13"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:15:37 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98888: Add .vulnerable file\n\nVulnerable from: 2dd0e8d2d2a15 (\"arm64: Kprobes with single stepping\nsupport\"), by Sandeepa Prabhu, merged in v4.8-rc1 (July 2016).\n\nThat commit created arch/arm64/kernel/probes/kprobes.c, and with it\nboth defects the fix repairs. Nothing between it and the fix changed the\nlogic.\n\nSuggested tag:\n\n\tFixes: 2dd0e8d2d2a1 (\"arm64: Kprobes with single stepping support\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 23f851ac0078a (\"arm64: kprobes: Allow reentering kprobes while\nsingle-stepping\", v7.2-rc5, no Fixes: tag) makes two connected changes:\n\n 1. In reenter_kprobe(), KPROBE_HIT_SS moves out of the BUG() arm and\n    into the recoverable arm, alongside KPROBE_HIT_ACTIVE and\n    KPROBE_HIT_SSDONE. Only KPROBE_REENTER (a second level of nesting)\n    still ends in BUG().\n\n 2. In struct prev_kprobe, it adds saved_irqflag.\n    save_previous_kprobe() and restore_previous_kprobe() now save and\n    restore it together with kp and status.\n\n2. How the bug happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe state machine\n-----------------\n\nA kprobe moves through these states in the per-CPU kprobe_ctlblk:\n\n - kprobe_brk_handler() sets KPROBE_HIT_ACTIVE and runs the pre_handler.\n\n - setup_singlestep() then sets KPROBE_HIT_SS and calls\n   kprobes_save_local_irqflag(). That stores\n\n\tkcb-\u003esaved_irqflag \u003d regs-\u003epstate \u0026 DAIF_MASK\n\n   masks all of DAIF, and points the PC at the out-of-line (XOL) slot.\n\n - After the XOL instruction runs, kprobe_ss_brk_handler() restores\nDAIF from kcb-\u003esaved_irqflag and calls post_kprobe_handler(), which\nsets KPROBE_HIT_SSDONE.\n\nA probe hit while another probe is active goes to reenter_kprobe().\nThere is exactly one save slot (kcb-\u003eprev_kprobe).\n\nThe window where the state is KPROBE_HIT_SS\n-------------------------------------------\n\nThe state stays KPROBE_HIT_SS from the moment setup_singlestep()\nreturns until the single-step handler runs. Instrumented code runs\ninside that window:\n\n - On the way out of the kprobe BRK:\n\n\tel1_brk64()\n\t  -\u003e debug_exception_exit()\n\t    -\u003e preempt_enable_no_resched()\n\t      -\u003e preempt_count_sub()\n\t        -\u003e trace_preempt_on()\n\t          -\u003e preemptirq:preempt_enable tracepoint\n\t            -\u003e perf\n\n   In the reporter\u0027s case perf goes on through perf_tp_event() -\u003e\n__perf_event_overflow() -\u003e perf_output_sample(), which copies the user\nstack for --call-graph dwarf. That copy can fault.\n\n - On the way into the step BRK:\n\n\tdebug_exception_enter()\n\t  -\u003e preempt_disable()\n\t    -\u003e trace_preempt_off()\n\t      -\u003e preemptirq:preempt_disable tracepoint\n\nAll of the perf, BPF and page-fault code on those paths can be kprobed.\nIf any of it is probed, the nested BRK finds cur_kprobe !\u003d NULL with\nstatus KPROBE_HIT_SS, and reenter_kprobe() prints \"Failed to recover\nfrom reentered kprobes.\" and calls BUG().\n\nEvidence\n--------\n\nThe reporter\u0027s crash on mainline (QEMU) shows exactly this window:\npreempt_count_sub \u003c- el1_brk64, with the PC at the probed\nfolio_wait_bit_common BRK (d4200080). The reproducer is\n\n\tperf record -e preemptirq:preempt_{disable,enable} --call-graph dwarf\n\nplus a kprobe on folio_wait_bit_common. That particular trace shows the\nsibling fault-handler bug, but the reentry this fix handles comes from\nthe same window. In review, Hongyan Xia confirmed this window is what\nthey hit, and Masami Hiramatsu agreed and asked for the saved_irqflag\nchange.\n\nWhy saved_irqflag has to be added\n---------------------------------\n\nOnce a hit in KPROBE_HIT_SS is allowed, the outer probe\u0027s original\nDAIF value is still waiting in kcb-\u003esaved_irqflag. The nested probe\nruns in debug-exception context, where DAIF is fully masked, and its\nsetup_singlestep() overwrites that field. When the outer step completes,\nkprobes_restore_local_irqflag() would apply the masked value and leave\ninterrupts disabled for good.\n\nBefore the fix this problem could not occur:\n\n - A reentry from HIT_ACTIVE happens before the outer probe saves its\n   flags.\n\n - A reentry from HIT_SSDONE happens after it has restored them.\n\n - A reentry from HIT_SS hit BUG() first.\n\nBoth halves come from the same assumption: reentry can only happen from\nhandler context, never during the single-step window.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on reenter_kprobe() shows only three commits before the fix:\n\n - 2dd0e8d2d2a15 (v4.8-rc1) creates the function with:\n\n\tcase KPROBE_HIT_SS:\n\tcase KPROBE_REENTER:\n\t\tpr_warn(\"Unrecoverable kprobe detected at %p.\\n\", p-\u003eaddr);\n\t\tdump_kprobe(p);\n\t\tBUG();\n\n - 0722867dcbc28 (2018) only changes the message, dropping the %p.\n\n - 9c89bb8e32720 (2021) only rewords it to \"Failed to recover from\n   reentered kprobes.\"\n\ngit log -L on struct prev_kprobe shows it was created in 2dd0e8d2d2a15\nas\n\n\tstruct prev_kprobe {\n\t\tstruct kprobe *kp;\n\t\tunsigned int status;\n\t};\n\nand never touched again until the fix. The same commit created the\nsingle per-CPU kcb-\u003esaved_irqflag and kprobes_save_local_irqflag().\nLater commits (b3980e48528c4, 7ee31a3aa8f49) only changed which PSTATE\nbits are saved and masked, not the single-slot design.\n\nThe arm64 port copied logic x86 had already fixed\n-------------------------------------------------\n\nBefore 2014, x86 also treated a hit during single-step as fatal. x86\nfixed that in 2014, in 6a5022a56ac3 (v3.16), two years before the arm64\nport landed. The arm64 code dates from 2013 (Linaro copyright) and kept\nthe old x86 behaviour. 32-bit ARM received the same fix as f3fbd7ec62dec\nin 2017. arm64 never did until now.\n\n4. Commits that are not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - d8bb6718c4db9 (\"arm64: Make debug exception handlers visible from\n   RCU\", Masami Hiramatsu, v5.3-rc3) made the bug easy to reach. It\n   added preempt_disable() and preempt_enable_no_resched() around the\n   debug handlers, which put the preempt tracepoints, and therefore perf\n   and BPF, inside the HIT_SS window.\n\n    - In the 2016 code, do_debug_exception() skipped its\n      trace_hardirqs_on() because kprobes had already set the I bit in\n      regs-\u003epstate. No easy perf/trace path into that window was found\n      in the 2016 code.\n\n    - Still, d8bb6718c4db9 only exposed the existing policy; the\n      faulty BUG() decision and the missing save slot are in\n      2dd0e8d2d2a15.\n\n - eaff68b328611 (v6.17) moved debug_exception_enter()/exit() into\n   entry-common.c. That is why the crash trace shows el1_brk64.\n\n - 7ee31a3aa8f49 (v5.10-rc3) switched from hardware single-step to a\n   BRK and to DAIF_MASK, and kept HIT_SS fatal.\n\n - 879a6754d3d11 is the sibling patch in the same series and fixes a\n   separate problem (faults not caused by the XOL slot).\n\nThe affected range is v4.8 up to v7.2-rc5, with an easy trigger from\nv5.3. That matches the CVE\u0027s stable fixes, which go back to 5.10.271.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "0b8002169036f664be3921d460a97f68c4045e13",
      "tree": "f7b18722b58d05614a795599e8382aa3427ba281",
      "parents": [
        "b13ecb4a729c7d0dd1d677f111f61391edd5a76e"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 12:05:02 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98887: Add .vulnerable file\n\nVulnerable from: 5d44fe7c9808c (\"mac80211_hwsim: add frame transmission\nsupport over virtio\"), by Erel Geron, first released in v5.7-rc1 (2020).\n\nThat commit changed the netlink policy type of HWSIM_ATTR_TX_INFO from\nNLA_UNSPEC to NLA_BINARY and kept the same .len. The value of .len did\nnot change, but its meaning did. With NLA_UNSPEC, .len is a minimum\nlength. With NLA_BINARY, it is a maximum length. The 9-year-old handler\nrelied on the minimum. From v5.7 on, a 0-7 byte attribute passes\nvalidation, and the handler reads 8 bytes from it.\n\nSuggested tag:\n\n\tFixes: 5d44fe7c9808 (\"mac80211_hwsim: add frame transmission support over virtio\")\n\nThe fix commit has no Fixes: tag.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 3dc723ac78a6e (\"wifi: mac80211_hwsim: reject undersized\nHWSIM_ATTR_TX_INFO\", v7.2-rc5) changes the policy entry:\n\n\t-\t[HWSIM_ATTR_TX_INFO] \u003d { .type \u003d NLA_BINARY,\n\t-\t\t\t\t .len \u003d IEEE80211_TX_MAX_RATES *\n\t-\t\t\t\t\tsizeof(struct hwsim_tx_rate)},\n\t+\t[HWSIM_ATTR_TX_INFO] \u003d\n\t+\t\tNLA_POLICY_EXACT_LEN(IEEE80211_TX_MAX_RATES *\n\t+\t\t\t\t     sizeof(struct hwsim_tx_rate)),\n\nThe code that consumes the attribute is\nhwsim_tx_info_frame_received_nl() in\ndrivers/net/wireless/virtual/mac80211_hwsim_main.c:\n\n\ttx_attempts \u003d (struct hwsim_tx_rate *)nla_data(info-\u003eattrs[HWSIM_ATTR_TX_INFO]);\n\t...\n\tfor (i \u003d 0; i \u003c IEEE80211_TX_MAX_RATES; i++) {\n\t\ttxi-\u003estatus.rates[i].idx   \u003d tx_attempts[i].idx;\n\t\ttxi-\u003estatus.rates[i].count \u003d tx_attempts[i].count;\n\t}\n\n - struct hwsim_tx_rate is { s8 idx; u8 count; } __packed, which is 2\n   bytes.\n\n - IEEE80211_TX_MAX_RATES is 4, so the loop always reads 8 bytes.\n\n - The handler never checks nla_len(). It relies entirely on the policy\n   to guarantee at least 8 bytes.\n\nUnder NLA_BINARY, validate_nla() only checks\n\n\tif (pt-\u003elen \u0026\u0026 attrlen \u003e pt-\u003elen)\n\t\tgoto out_err;\n\nso any length from 0 to 8 passes.\n\n2. The original design was safe: 7882513bacb17 (v3.1, 2011)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nJavier Lopez\u0027s commit 7882513bacb17 (\"mac80211_hwsim driver support\nuserspace frame tx/rx\") added HWSIM_CMD_TX_INFO_FRAME, the handler, the\nsame loop, and this policy:\n\n\t[HWSIM_ATTR_TX_INFO] \u003d { .type \u003d NLA_UNSPEC,\n\t\t\t\t .len \u003d IEEE80211_TX_MAX_RATES*sizeof(struct hwsim_tx_rate)},\n\nvalidate_nla() in that tree (lib/nlattr.c at 7882513bacb17) sends\nNLA_UNSPEC to the default: branch:\n\n\tdefault:\n\t\tif (pt-\u003elen)\n\t\t\tminlen \u003d pt-\u003elen;\n\t\t...\n\t\tif (attrlen \u003c minlen)\n\t\t\treturn -ERANGE;\n\nSo .len was a minimum length, and an undersized attribute was rejected\nbefore the handler ran. At that time IEEE80211_TX_MAX_RATES was 5,\ngiving a 10-byte minimum and a 10-byte read. It later became 4, but the\npolicy and the loop use the same macro, so they always stayed matched.\n\n3. The minimum-length guarantee held until v5.6\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe default: branch of validate_nla() is identical at v3.14, v4.14,\nv4.20 and v5.1. The 2019 netlink rework kept the behaviour, as\nlib/nlattr.c at 5d44fe7c9808c^ shows:\n\n\tcase NLA_UNSPEC:\n\t\tif (validate \u0026 NL_VALIDATE_UNSPEC) { ... return -EINVAL; }   /* strict mode only */\n\t\t/* fall through */\n\tcase NLA_MIN_LEN:\n\t\tif (attrlen \u003c pt-\u003elen)\n\t\t\tgoto out_err;\n\nNone of the hwsim commits in this window changed the guarantee:\n\n - 9ddd12af103f6 (\"mac80211_hwsim: minor netlink cleanups\", v3.14) only\n   reformatted the .len expression. The type stayed NLA_UNSPEC.\n\n - ef6243acb4782d (\"genetlink: optionally validate strictly/dumps\",\n   v5.2) added GENL_DONT_VALIDATE_STRICT to every hwsim op. That keeps\n   the genetlink path in liberal mode, so NL_VALIDATE_UNSPEC is not set\n   and the minimum-length check still applies.\n\n4. The breaking commit: 5d44fe7c9808c (v5.7-rc1)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIts commit message says: \"It also required fixing up the netlink\npolicy, since NLA_UNSPEC attributes are no longer accepted.\"\n\nThe commit added a virtio transport. hwsim_virtio_handle_cmd() parses\nincoming messages with genlmsg_parse(), which always uses\nNL_VALIDATE_STRICT. Strict mode includes NL_VALIDATE_UNSPEC, which\nrejects every NLA_UNSPEC attribute outright. So the commit had to move\nevery NLA_UNSPEC entry in hwsim_genl_policy to a real type:\n\n\t-\t[HWSIM_ATTR_ADDR_RECEIVER] \u003d { .type \u003d NLA_UNSPEC, .len \u003d ETH_ALEN },\n\t+\t[HWSIM_ATTR_ADDR_RECEIVER] \u003d NLA_POLICY_ETH_ADDR_COMPAT,\n\t ...\n\t-\t[HWSIM_ATTR_TX_INFO] \u003d { .type \u003d NLA_UNSPEC,\n\t+\t[HWSIM_ATTR_TX_INFO] \u003d { .type \u003d NLA_BINARY,\n\t \t\t\t\t .len \u003d IEEE80211_TX_MAX_RATES *\n\t \t\t\t\t\tsizeof(struct hwsim_tx_rate)},\n\n - The address attributes kept their lower bound.\n   NLA_POLICY_ETH_ADDR_COMPAT is NLA_EXACT_LEN_WARN(ETH_ALEN).\n\n - TX_INFO lost its lower bound. It became NLA_BINARY, where the same\n   .len now means \"at most 8 bytes\".\n\n - The correct type was already available. NLA_MIN_LEN (6f455f5f4e9c2,\n   v5.2) or NLA_EXACT_LEN would have kept the old guarantee. The fix\n   uses the exact-length form.\n\nBoth entry points were affected from this commit on, because both use\nhwsim_genl_policy:\n\n 1. Genetlink path (HWSIM_CMD_TX_INFO_FRAME, liberal mode): this path\n    had been safe since 2011. It now accepts a 0-7 byte TX_INFO.\n\n 2. Virtio path (hwsim_virtio_handle_cmd() calls\n    hwsim_tx_info_frame_received_nl()): this path was new in the same\n    commit and had the hole from the start. In virtio mode the handler\n    also skips the netgroup and wmediumd portid checks.\n\n5. How the bug triggers\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. A process sends HWSIM_CMD_REGISTER, which requires\n    GENL_UNS_ADMIN_PERM, i.e. CAP_NET_ADMIN over the netns\u0027s user\n    namespace. It becomes data2-\u003ewmediumd.\n\n 2. A hwsim radio transmits a frame. The skb is queued on\n    data2-\u003epending, and its cookie is sent to that process in\n    HWSIM_CMD_FRAME.\n\n 3. The process replies with HWSIM_CMD_TX_INFO_FRAME and valid\n    ADDR_TRANSMITTER, FLAGS, COOKIE and SIGNAL attributes.\n    HWSIM_ATTR_TX_INFO is given a length of 0-7, ideally as the last\n    attribute.\n\n 4. Validation passes, the cookie matches, and the loop reads 8 bytes\n    from nla_data(). The bytes past the payload come from the next\n    attributes or from past the end of the netlink message, into skb\n    tailroom or skb_shared_info. In the virtio case they come from the\n    reused receive buffer.\n\n 5. Those stray bytes become txi-\u003estatus.rates[].idx and .count. They\n    are passed to ieee80211_tx_status_irqsafe(), where rate control and\n    status reporting consume them.\n\n6. Commits ruled out as root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 7882513bacb17 (v3.1): Added the handler and loop, but NLA_UNSPEC +\n   .len enforced a minimum length. Safe for 9 years.\n\n - 9ddd12af103f6 (v3.14): Whitespace reformat of .len. Type unchanged.\n\n - ef6243acb4782d (v5.2): Opted hwsim out of strict validation, which\n   preserved the minimum-length check.\n\n - 5d44fe7c9808c (v5.7): Changed NLA_UNSPEC to NLA_BINARY, turning .len\n   from a minimum into a maximum. This introduced the bug.\n\n - 3dc723ac78a6e (v7.2-rc5): The fix, NLA_POLICY_EXACT_LEN(8).\n\nThe affected range is v5.7-rc1 up to the fix in v7.2-rc5.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "b13ecb4a729c7d0dd1d677f111f61391edd5a76e",
      "tree": "595c8ad55e84073595351e852e903df97e99c787",
      "parents": [
        "815decd151a4f6e6231bfff0bc44bc7b964d685d"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 11:58:07 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98886: Add .vulnerable file\n\nVulnerable from: 1e585b52fd8ea (\"gma500: Add the Oaktrail HDMI\nsupport\"), by Alan Cox, 2011-07-15, first released in v3.1-rc1. Both\ndefects that the fix removes were in the code from its first day in the\ntree, and nothing since has changed how it behaves.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 9b5ce5c496efd (\"drm/gma500: return errors from Oaktrail HDMI\nI2C reads\") changes drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c in two\nplaces.\n\n1a. The wait loop in xfer_read()\n--------------------------------\n\nBefore the fix:\n\n\twhile (i2c_dev-\u003estatus !\u003d I2C_TRANSACTION_DONE)\n\t\twait_for_completion_interruptible_timeout(\u0026i2c_dev-\u003ecomplete,\n\t\t\t\t\t\t\t  10 * HZ);\n\n\treturn 0;\n\nwait_for_completion_interruptible_timeout() returns one of three\nthings: a positive value when the completion fired, 0 on timeout, or\n-ERESTARTSYS when a signal is pending. The loop throws the return value\naway. Only the IRQ handler can end it, by setting status \u003d\nI2C_TRANSACTION_DONE, and it does that only when it sees\nHDMI_INTR_I2C_DONE. That leaves two failure modes:\n\n - The DONE interrupt never arrives. Possible causes are a dead or\n   absent sink, a stuck controller, or an IRQ that isn\u0027t delivered.\nThe same commit also enables HDMI_INTR_I2C_ERROR in\nhdmi_i2c_irq_enable(), but the handler has no case for it. An error\ninterrupt therefore just calls complete() with status still at\nI2C_STAT_INIT, and the loop goes back to sleep. The result is\n   10-second timeouts repeating forever.\n\n - A signal arrives during the wait. do_wait_for_common() checks\n   signal_pending_state() before it schedules, so every later call\n   returns -ERESTARTSYS immediately. The loop becomes a tight in-kernel\n   busy spin. The task never gets back to user space, so the signal is\n   never delivered, and SIGKILL can\u0027t stop it.\n\nIn both cases the task holds i2c_dev-\u003ei2c_lock and the adapter\u0027s bus\nlock (taken by i2c_transfer()) forever. Every other user of adapter 3,\nand the teardown path through i2c_del_adapter(), is blocked behind it.\n\nThe fix stores the return value, returns it when it is negative, and\nreturns -ETIMEDOUT when it is 0.\n\n1b. The return value of oaktrail_hdmi_i2c_access()\n--------------------------------------------------\n\noaktrail_hdmi_i2c_access() is the .master_xfer callback. It always\nreturned i (equal to num), meaning \"all messages transferred\". The I2C\ncore contract is to return a negative errno on failure, so callers could\nnever see an error. The fix stores the per-message result of\nxfer_read()/xfer_write(), stops at the first error and returns it.\n\n2. History of the code\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 1e585b52fd8ea \"gma500: Add the Oaktrail HDMI support\" (v3.1-rc1):\nCreates drivers/staging/gma500/mrst_hdmi_i2c.c with both bugs: the same\nunchecked loop with \"return 0\", and an access function that assigns \"err\n\u003d xfer_read(...)\" but then does \"return i\".\n\n - 1b082ccf59011 \"gma500: Add Oaktrail support\" (v3.3-rc1): Copies the\n   staging file into drivers/gpu/drm/gma500/oaktrail_hdmi_i2c.c,\n   renaming mrst_ to oaktrail_. The logic is identical.\n\n - b7cdd9e6323af \"staging: delete gma500 driver\" (v3.3-rc4): Deletes\n   the staging copy.\n\n - 1e30296a60381 \"oaktrail_hdmi_i2c_access() drop unused variable\"\n(v3.4-rc1): Removes the dead err. No behavior change, because the value\nwas never used and xfer_read() could only return 0 anyway.\n\n - 5f503148efdda \"gma500: Sync up Oaktrail HDMI\" (v3.5-rc1): Adds the\n   oaktrail_hdmi_setup() call in chip setup, which is what makes the\n   drm/gma500 copy reachable.\n\n - 16735d022f72b (v3.13-rc1): INIT_COMPLETION becomes\n   reinit_completion. Cosmetic.\n\n - e965a70727676, f6e8dc9edf963, 69050f8d6d075, bf4afc53b77ae (later):\n   Unrelated treewide cleanups.\n\nSupporting evidence:\n\n - git blame -C -C on the pre-fix lines attributes the while /\n   wait_for_completion_interruptible_timeout / return 0 lines to\n   1e585b52fd8ea in drivers/staging/gma500/mrst_hdmi_i2c.c.\n\n - Searching history under drivers/staging and drivers/gpu/drm for\n   I2C_TRANSACTION_DONE, the identifier the loop waits on, finds only\n   1e585b52fd8ea -\u003e 1b082ccf59011 -\u003e b7cdd9e6323af.\n\n - git grep of the tree just before 1e585b52fd8ea finds no earlier copy.\n\n3. Why 1e585b52fd8ea and not the later commits\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - The code was reachable from day one. In the staging driver,\n   mrst_chip_setup() called mrst_hdmi_setup(), which called\n   mrst_hdmi_i2c_init() whenever PCI device 8086:080d was present. That\n   registered mrst_hdmi_i2c_adapter (.nr \u003d 3) with the broken\n   .master_xfer.\n\n - 1b082ccf59011 is only a copy. It moved the code into a new location\n   without changing it. If a commit is needed for the drm/gma500 path\n   specifically, use this one.\n\n - 5f503148efdda only wired up a call. It made the copied code\n   reachable in drm/gma500 (from v3.5) but did not introduce the defect.\n\n - 1e30296a60381 removed a dead variable. It did not create the\n   \"ignore errors\" behavior, which was already there.\n\n4. Impact\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe in-kernel EDID path never uses this adapter.\noaktrail_hdmi_get_modes() still has\n\"/* FIXME ? edid \u003d drm_get_edid(connector, i2c_adap); */\" and uses a\nhardcoded raw_edid. So the realistic trigger is a user-space or other\nI2C client sending a read through adapter 3. The usual example is the\nI2C_RDWR ioctl on /dev/i2c-3, which by default needs root.\n\nThe effect is a permanently hung (or, with a signal pending, spinning)\nunkillable task holding the bus lock. It only affects Oaktrail (GMA600)\nhardware with CONFIG_DRM_GMA600 (in staging, CONFIG_DRM_PSB_MRST).\n\nThe affected range is v3.1 up to the fix, which is in origin/master and\nfirst appeared in next-20260723. The staging copy covered v3.1 to v3.3,\nand the drm/gma500 copy has been reachable from v3.5.\n\nSuggested tag:\n\n\tFixes: 1e585b52fd8e (\"gma500: Add the Oaktrail HDMI support\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "815decd151a4f6e6231bfff0bc44bc7b964d685d",
      "tree": "cee48e9f6fdfcaa053d8a785e950cb99c94a5c79",
      "parents": [
        "4b309232152e611d4106769ad6ed4489909f398f"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 11:25:17 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98885: Add .vulnerable file\n\nVulnerable from: 07f6a79415d7d (\"ima: add appraise action keywords and\ndefault rules\"), by Mimi Zohar, first released in v3.7. This is the\nfirst commit at which IMA\u0027s fix-mode xattr write can actually run.\n\nDefect origin: 2fe5d6def1672 (\"ima: integrity appraisal extension\"), by\nMimi Zohar, 2012-02-13, also first released in v3.7. That commit\ncreated ima_fix_xattr(). From the start it wrote security.ima through\n__vfs_setxattr_noperm(), which skips the VFS checks for read-only\nfilesystems and immutable inodes, and never did those checks itself.\nEvery later change to the function only reshaped it. The fix adds the\nmissing checks to that same function. At 2fe5d6def1672 itself nothing\ncould reach the write (section 4), so it is not the became-vulnerable\nboundary. 07f6a79415d7d is the next commit in the same series, and no\nrelease ever contained one commit without the other.\n\n\tFixes: 2fe5d6def167 (\"ima: integrity appraisal extension\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 69fc6474236d9 (\"ima: return error early if file xattr cannot be\nchanged\"), in v7.2-rc1, has no Fixes: tag. It adds two checks at the top\nof ima_fix_xattr():\n\n\tif (IS_RDONLY(d_inode(dentry)))\n\t\treturn -EROFS;\n\tif (IS_IMMUTABLE(d_inode(dentry)))\n\t\treturn -EPERM;\n\nThe crash in the fix\u0027s commit message happens like this:\n\n 1. During early boot, systemd-sysroot opens a file on the root\n    filesystem, which is still mounted read-only.\n\n 2. ima_file_check() -\u003e process_measurement() -\u003e\n    ima_appraise_measurement() runs, and the appraisal fails (missing\n    or bad hash).\n\n 3. The kernel was booted with ima_appraise\u003dfix, so the fix-mode branch\n    calls ima_fix_xattr().\n\n 4. ima_fix_xattr() calls __vfs_setxattr_noperm() -\u003e ext4_xattr_set()\n    -\u003e ext4_journal_start() -\u003e ext4_journal_check_start().\n\n 5. That function hits WARN_ON_ONCE(sb_rdonly(sb)).\n\n2. Why this is a contract violation \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe VFS keeps \"can this inode\u0027s xattrs be changed?\" outside the\nfilesystem\u0027s -\u003eset handler:\n\n - Read-only: the syscall path calls mnt_want_write() /\n   mnt_want_write_file() before setting an xattr.\n\n - Immutable / append-only: vfs_setxattr() calls xattr_permission(). In\n   v3.7 that function opened with the comment \"We can never set or\n   remove an extended attribute on a read-only filesystem or on an\n   immutable / append-only inode\" and returned -EPERM for\n   IS_IMMUTABLE/IS_APPEND. Today the same check is in may_write_xattr().\n\n - __vfs_setxattr_noperm() is the low-level helper below all of that.\nIt goes straight to the filesystem handler, so the caller has to make\nsure the write is allowed. ext4\u0027s xattr set path has no\nIS_RDONLY or IS_IMMUTABLE check of its own (confirmed with git grep on\norigin/master).\n\nThe fix\u0027s two checks, and their error codes (-EROFS, -EPERM), are\nexactly the two VFS checks IMA was skipping.\n\n3. The defect origin: 2fe5d6def1672\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n2fe5d6def1672 created security/integrity/ima/ima_appraise.c with this\ncode:\n\n\tstatic void ima_fix_xattr(struct dentry *dentry,\n\t\t\t\t  struct integrity_iint_cache *iint)\n\t{\n\t\tiint-\u003edigest[0] \u003d IMA_XATTR_DIGEST;\n\t\t__vfs_setxattr_noperm(dentry, XATTR_NAME_IMA,\n\t\t\t\t      iint-\u003edigest, IMA_DIGEST_SIZE + 1, 0);\n\t}\n\t...\n\tout:\n\t\tif (status !\u003d INTEGRITY_PASS) {\n\t\t\tif (ima_appraise \u0026 IMA_APPRAISE_FIX) {\n\t\t\t\tima_fix_xattr(dentry, iint);\n\t\t\t\tstatus \u003d INTEGRITY_PASS;\n\t\t\t}\n\nThe same commit added the second caller, ima_update_xattr(), invoked\nfrom ima_check_last_writer(). Three things in this commit make up the\nbug:\n\n - It picked the no-check helper. Writing through\n__vfs_setxattr_noperm() skips both the mount read-only check and the\nimmutable/append check.\n\n - It writes on any failed appraisal. ima_appraise_measurement() runs\n   from process_measurement() for every policy-matched open, including\nO_RDONLY opens, exec and mmap. So in fix mode, simply reading a file\nwith a bad or missing hash makes the kernel write to it, whatever the\nmount state or inode flags.\n\n - It never checked the inode first. There was no\n   IS_RDONLY/IS_IMMUTABLE test, which is the exact gap the fix closes.\n\n4. Why 07f6a79415d7d is the boundary, not 2fe5d6def1672\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAt 2fe5d6def1672, ima_fix_xattr() has exactly two callers, and both\nneed the IMA_APPRAISE bit (0x04):\n\n - ima_appraise_measurement(), called only from process_measurement()\n   when \"action \u0026 IMA_APPRAISE\". action comes from\n   ima_must_appraise_or_measure() -\u003e ima_match_policy(), which returns\n   entry-\u003eaction \u0026 (IMA_APPRAISE | IMA_MEASURE).\n\n - ima_update_xattr(), called from ima_check_last_writer() only when\n   \"iint-\u003eflags \u0026 IMA_APPRAISE\".\n\nNothing at that commit can produce IMA_APPRAISE:\n\n - The default rules (default_rules[]) contain only MEASURE and\n   DONT_MEASURE entries.\n\n - The securityfs policy parser knows only \"measure\" and\n   \"dont_measure\". Any other token returns -EINVAL, and a rule with no\n   action (entry-\u003eaction \u003d\u003d UNKNOWN) is rejected with -EINVAL too. So a\n   loaded policy cannot contain an APPRAISE rule.\n\n - iint-\u003eflags gets IMA_APPRAISE in only two places:\nprocess_measurement() (\"iint-\u003eflags |\u003d action\", where action comes from\nthe policy above) and ima_inode_post_setattr() (\"if\n(must_appraise) iint-\u003eflags |\u003d IMA_APPRAISE\"). must_appraise comes from\nima_must_appraise(), which at this commit is a stub that returns 0.\n\nSo with ima_appraise\u003dfix on the command line, the write is still dead\ncode at 2fe5d6def1672. Marking it as the vulnerable point would claim a\ncommit was affected when the failure could not happen there.\n\n07f6a79415d7d is the first commit where the write can run. It:\n\n - adds the \"appraise\" and \"dont_appraise\" keywords to the policy\n   parser (entry-\u003eaction \u003d APPRAISE / DONT_APPRAISE);\n\n - adds default_appraise_rules[] (APPRAISE for fowner \u003d\u003d 0, with\nDONT_APPRAISE for pseudo filesystems), enabled by the new\nima_appraise_tcb boot parameter;\n\n - replaces the ima_must_appraise() stub with\n   \"return ima_match_policy(inode, func, mask, IMA_APPRAISE);\".\n\nFrom this commit on, booting with ima_appraise\u003dfix and either\nima_appraise_tcb or a loaded \"appraise\" rule makes process_measurement()\ncall ima_appraise_measurement(). Any failed appraisal then reaches\nima_fix_xattr() -\u003e __vfs_setxattr_noperm() regardless of read-only or\nimmutable state. ima_update_xattr() is reachable the same way, through\nima_check_last_writer().\n\n07f6a79415d7d only wired up policy; the faulty write logic was written\nin 2fe5d6def1672. 2fe5d6def1672 is an ancestor of 07f6a79415d7d, there\nare no other commits between them, and both first ship in v3.7-rc1. So\nthe affected version range is the same whichever one is recorded.\n\n5. Later history: refactors and one partial fix\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on ima_fix_xattr() in origin/master shows:\n\n - 2fe5d6def1672 (v3.7-rc1): Introduced the unchecked write.\n\n - 07f6a79415d7d (v3.7-rc1): Made it reachable through policy (see\n   above).\n\n - 5a44b41207174 (v3.7-rc1): Changed the xattr layout (ima_xattr\n   struct); no behaviour change.\n\n - def3e8b9ee23c (v3.9-rc1): Partial: made ima_fix_xattr() return the\n   error and set INTEGRITY_PASS only on success (details below). The\n   write is still attempted.\n\n - c7c8bb237fdbf, a35c3fb6490cc, 3ea7a56067e66 (later): Hash-algorithm\n   support and dynamically allocated hash storage; layout only.\n\n - c7c7a1a18af4c, 39f60c1ccee72 (later): idmapped-mount argument\n   plumbing.\n\n - 4de2f084fbff4 (later): Rename to ima_iint_cache.\n\n - 69fc6474236d9 (v7.2-rc1): The fix: adds the RO/immutable checks.\n\ndef3e8b9ee23c (\"ima: set appraise status in fix mode only when xattr is\nfixed\") is worth a closer look. Its message says outright: \"When a file\nsystem is mounted read-only, setting the xattr value in fix mode fails\nwith an error code -EROFS.\" It treated the -EROFS as an acceptable\nresult and only stopped IMA from reporting a pass. It did not stop the\nwrite attempt. That relied on each filesystem rejecting the write\nquietly.\n\n6. Why it only became visible in v6.6\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAt the time, ext4\u0027s ext4_journal_check_start() just returned -EROFS on\na read-only superblock. So the illegal call failed silently and the bug\nstayed hidden. In e7fc2b31e04c4 (\"ext4: warn on read-only filesystem in\next4_journal_check_start()\", Jan Kara, v6.6), that check became\nWARN_ON_ONCE(sb_rdonly(sb)). Jan\u0027s reasoning was that reaching it now\nindicates a programming error, and IMA was exactly such a caller. From\nv6.6 on, the first fix-mode appraisal failure on a read-only ext4 root\ntriggers the WARN, which becomes a boot-time panic if panic_on_warn is\nset. That ext4 commit exposed the IMA bug; it did not cause it.\n\n7. The immutable half\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe IS_IMMUTABLE part had a real effect from day one. ext4\u0027s xattr set\npath never checks immutability, because it relies on the VFS for that.\nSo since v3.7, fix mode has silently rewritten security.ima on chattr\n+i files when appraisal failed. That breaks the immutable guarantee, and\nno warning was ever printed.\n\n8. Summary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Vulnerable from (recorded in .vulnerable): 07f6a79415d7d (\"ima: add\n   appraise action keywords and default rules\"), v3.7-rc1, the first\n   commit where the unchecked write is reachable.\n\n - Defect origin: 2fe5d6def1672 (\"ima: integrity appraisal extension\"),\n   v3.7-rc1, which wrote the unchecked\n   __vfs_setxattr_noperm() call. That call was dead code until\n   07f6a79415d7d.\n\n - Partial mitigation: def3e8b9ee23c (v3.9) handled the error but still\n   attempted the write.\n\n - Made visible by: e7fc2b31e04c4 (v6.6, ext4 WARN on read-only journal\n   start).\n\n - Affected range: v3.7 up to the fix in v7.2-rc1, for systems that\n   boot with ima_appraise\u003dfix.\n\n - Suggested tag, pointing at where the faulty code was written. Both\n   commits first ship in v3.7-rc1, so the backport range is the same:\n\n\tFixes: 2fe5d6def167 (\"ima: integrity appraisal extension\")\n\n9. Related gap not covered by the fix\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe sibling branch in ima_appraise_measurement()\n(status \u003d\u003d INTEGRITY_NOLABEL -\u003e evm_fix_hmac()) also writes xattrs with\nno read-only or immutable check, and this fix doesn\u0027t cover it.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "4b309232152e611d4106769ad6ed4489909f398f",
      "tree": "1064c813884e3a95cd50868c82d753b8d19cf64f",
      "parents": [
        "ce399ce8d5e543bbfa09a422b6786b561f3636b4"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 11:15:07 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98884: Add .vulnerable file\n\nRoot cause: 05e5b136459b1 (\"mmc: Add missing sg_init_table() call\"), by\nHaavard Skinnemoen, November 2007, first released in v2.6.24-rc4. It is\nthe root cause of the bug fixed by 3e0483e93a8b (\"mmc: core: Add\nvalidation for host-provided max_segs\").\n\nThat commit added \"sg_init_table(mq-\u003esg, host-\u003emax_phys_segs)\" to\nmmc_init_queue(). The allocation right before it was sized from that\nsame host-supplied unsigned short and was only checked for NULL. When\nthe segment count is 0, this exact sequence still crashes today; only\nthe names have changed since then. The fix commit has no Fixes: tag, so\nthis was traced by hand.\n\n1. What the fix addresses\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe code just before the fix, in drivers/mmc/core/queue.c:\n\n\tstatic struct scatterlist *mmc_alloc_sg(unsigned short sg_len, gfp_t gfp)\n\t{\n\t\tsg \u003d kmalloc_objs(*sg, sg_len, gfp);\n\t\tif (sg)\n\t\t\tsg_init_table(sg, sg_len);\n\t\treturn sg;\n\t}\n\n\tstatic unsigned short mmc_get_max_segments(struct mmc_host *host)\n\t{\n\t\treturn host-\u003ecan_dma_map_merge ? MMC_DMA_MAP_MERGE_SEGMENTS : host-\u003emax_segs;\n\t}\n\n\tstatic int mmc_mq_init_request(...)\n\t{\n\t\tmq_rq-\u003esg \u003d mmc_alloc_sg(mmc_get_max_segments(host), GFP_KERNEL);\n\t\tif (!mq_rq-\u003esg)\n\t\t\treturn -ENOMEM;\n\nThis is how a zero segment count crashes:\n\n 1. struct mmc_host::max_segs is an unsigned short. If a host driver\n    assigns 65536 or any multiple of 0x10000, the value wraps to 0. A\n    driver could also set 0 directly. The MMC core has never checked\n    this value: mmc_alloc_host() defaults it to 1 and drivers overwrite\n    it freely.\n\n 2. blk_mq_alloc_tag_set(), called from mmc_init_queue(), runs\n    mmc_mq_init_request() for every tag. That calls mmc_alloc_sg(0).\n\n 3. kmalloc_objs(*sg, 0) is a zero-size allocation, so slab returns\n    ZERO_SIZE_PTR, which is (void *)16. That is not NULL, so the\n    \"if (sg)\" check passes.\n\n 4. sg_init_table(sg, 0) does memset(sgl, 0, 0) and then\n    sg_init_marker(), which calls sg_mark_end(\u0026sgl[nents - 1]). nents is\n    an unsigned int, so nents - 1 is 0xffffffff.\n\n 5. On arm64, sizeof(struct scatterlist) is 32. The address becomes\n    0x10 + 0xffffffff * 0x20 \u003d 0x1ffffffff0. That exactly matches the\n    oops in the fix\u0027s commit message: the fault address is\n    0000001ffffffff0, x8 \u003d 0x1fffffffe0 and x0 \u003d 0x10. The\n    read-modify-write of page_link faults inside sg_init_table+0x2c.\n\nIf initialization had survived, blk_validate_limits() would turn\nlim.max_segments \u003d 0 into BLK_MAX_SEGMENTS (128). blk_rq_map_sg() would\nthen write up to 128 entries into a table with no entries. The fix\u0027s\n-EINVAL closes that path too.\n\n2. History of the vulnerable code\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 1da177e4c3f41 (2.6.12-rc2, pre-git):\nmq-\u003esg \u003d kmalloc(sizeof(struct scatterlist) * host-\u003emax_phys_segs),\nwhere max_phys_segs was an unsigned short. No validation, but also no\nsg_init_table().\n\n - 6cb8f91320d3e (SLUB part: 272c1d21d6fe4) (v2.6.23-rc1): Slab\n   allocators start returning ZERO_SIZE_PTR (non-NULL) for kmalloc(0).\n\n - d3ad0aa424895 (v2.6.24-rc1): Changed the allocation to kzalloc.\n   Still no sg_init_table() on this path.\n\n - 45711f1af6eff (v2.6.24-rc1): Added sg_init_table() only on the\n   bounce-buffer path, with fixed sizes unrelated to max_segs.\n\n - 05e5b136459b1 (v2.6.24-rc4): Back to kmalloc(sizeof(sg) *\n   host-\u003emax_phys_segs), plus a NULL check, plus sg_init_table(mq-\u003esg,\n   host-\u003emax_phys_segs). This is the crashing pattern.\n\n - a36274e018419 (v2.6.37-rc1): Renamed max_phys_segs/max_hw_segs to a\n   single max_segs and kept the unsigned short type. Same code\n   otherwise.\n\n - 97868a2bdfc2f (v3.1-rc1): Moved the pattern into a new helper,\n   mmc_alloc_sg(), as kmalloc + \"if (sg)\" + sg_init_table(sg, sg_len).\n   Pure refactor.\n\n - 64e29e42a61b8, c5bda0ca6fab8, 7b410d074b253 (v4.10-v4.12):\n   Per-queue-depth and shared-request refactors. Pattern unchanged.\n\n - 304419d8a7e92, de3ee99b097dd (v4.13-v4.14): Moved the allocation\n   into the block layer\u0027s per-request init (mmc_init_request) and\n   deleted the bounce path, leaving mmc_alloc_sg(host-\u003emax_segs, gfp).\n\n - 81196976ed946 (v4.16-rc1): blk-mq conversion:\nmmc_mq_init_request -\u003e __mmc_init_request -\u003e\nmmc_alloc_sg(host-\u003emax_segs). This is the frame in the oops call trace.\n\n - 38c38cb732232 (v5.4-rc1): Added mmc_get_max_segments(). With an\nIOMMU merge boundary, max_segs \u003c 512 sets can_dma_map_merge, so a 0\nbecomes 512 and the bug is hidden on IOMMU-backed hosts.\n\n - f6f607070aa6f (v5.15-rc1): Changed the helper types to unsigned\n   short. Harmless, since the source was already a u16.\n\n - 69050f8d6d075 (2026): kmalloc_array -\u003e kmalloc_objs. Mechanical.\n\n - 3e0483e93a8be (next-20260519): The fix: reject sg_len \u003d\u003d 0 with\n   -EINVAL.\n\ndrivers/mmc/core/{host,core}.c never validated max_segs, so the fix is\nnot restoring a check that was later removed.\n\n3. Why 05e5b136459b1 and not an earlier or later commit\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Not 1da177e4c3f41. Its code had the unvalidated u16 sizing but did\n   nothing harmful with a 0 count at queue init. The allocation was only\n   memset or zeroed, which is a no-op for size 0.\nblk_queue_max_phys_segments(q, 0) raised the limit to 1 with a printk,\nand SLAB\u0027s kmalloc(0) in 2.6.12 returned a real 32-byte object. The\nout-of-bounds sg_mark_end(\u0026sgl[nents - 1]) that the fix guards against\ndid not exist yet.\n\n - 05e5b136459b1 creates the bug as reported. It added\n   sg_init_table(mq-\u003esg, host-\u003emax_phys_segs) right after an allocation\n   sized by the same unchecked value and guarded only by\n\"if (!mq-\u003esg)\". ZERO_SIZE_PTR was already in mainline (v2.6.23-rc1\npredates this commit; the ancestry was verified). So from v2.6.24-rc4\non, a 0 count produced exactly this sequence: a zero-size allocation\npasses the NULL check and sg_init_table() writes to sgl[UINT_MAX]. Every\nlater commit just carried this pattern along.\n\n - Not the later commits. a36274e01841, 97868a2bdfc2, 304419d8a7e9,\n   de3ee99b097d, 81196976ed94, 38c38cb73223 and f6f607070aa6 only\n   renamed, moved, factored or retyped the code. 38c38cb732232 actually\n   narrows the exposure on IOMMU systems. None of them introduced the\n   missing zero check.\n\n4. Practical notes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Affected range: v2.6.24-rc4 onward, which covers every supported\n   stable tree. On a non-IOMMU host, or one without a DMA merge\n   boundary, any max_segs that truncates to 0 crashes during card probe\n   in mmc_rescan, as soon as the block queue is created.\n\n - Real-world trigger: no mainline host driver was found that currently\n   truncates max_segs to 0. For example, dw_mmc\u0027s desc_num is\nPAGE_SIZE / sizeof(desc), at most 4096. The oops in the fix\u0027s commit\nmessage came from a locally modified (-dirty) linux-next tree on an\nRK3576 dw_mmc board. The fix is a defensive hardening of the core\nagainst invalid driver input.\n\n - Suggested tag:\n\n\tFixes: 05e5b136459b (\"mmc: Add missing sg_init_table() call\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "ce399ce8d5e543bbfa09a422b6786b561f3636b4",
      "tree": "5cd964872ac63a250fb06965868bec86aaa99f69",
      "parents": [
        "676a81a16254e43b1f45ce0cba77feee9b6afc3b"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 11:05:58 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98883: Add .vulnerable file\n\nRoot cause: 7c7e6c8924e7 (\"tty: serial: handle HAS_IOPORT\ndependencies\"), by Niklas Schnelle and Arnd Bergmann, 2024-10-24, first\nreleased in v6.13-rc1. That commit turned the catch-all default: branch\nof set_io_from_upio() into an \"unsupported iotype\" WARN. It did not add\na case for UPIO_AU (iotype 4), even though Alchemy and Ralink ports\nstill pass that iotype through this function on every registration.\n\n\tFixes: 7c7e6c8924e7 (\"tty: serial: handle HAS_IOPORT dependencies\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix 49fa670efdfdd (\"serial: 8250_port: recognize UPIO_AU\") adds an\nempty case to the switch in set_io_from_upio()\n(drivers/tty/serial/8250/8250_port.c):\n\n\t+\tcase UPIO_AU:\n\t+\t\tbreak;\n\t \tdefault:\n\t \t\tWARN(p-\u003eiotype !\u003d UPIO_PORT || p-\u003eiobase,\n\t \t\t     \"Unsupported UART type %x\\n\", p-\u003eiotype);\n\nThe reported symptom is a WARN during probe on MIPS Alchemy:\n\n\tWARNING: drivers/tty/serial/8250/8250_port.c:462 at set_io_from_upio+0xfc/0x124\n\tUnsupported UART type 4\n\t  set_io_from_upio \u003c- serial8250_set_defaults \u003c- serial8250_register_8250_port \u003c- serial8250_probe\n\nUPIO_AU \u003d SERIAL_IO_AU \u003d 4 is a valid iotype, defined in\ninclude/linux/serial_core.h:436. The function simply had no case for it.\n\n2. How UPIO_AU reaches set_io_from_upio()\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThere are two producers of UPIO_AU ports, both in 8250_rt288x.c:\n\n - Alchemy: arch/mips/alchemy/common/platform.c calls\n   au_platform_setup() on each plat_serial8250_port. That sets iotype \u003d\n   UPIO_AU, serial_in/out \u003d au_serial_in/out and dl_read/write \u003d\n   au_serial_dl_read/write. serial8250_probe()\n   (8250_platform.c:170-181) copies these into a uart_8250_port.\n\n - Ralink: 8250_of.c:204 calls rt288x_setup(port) for the\n   \"ralink,rt2880-uart\" compatible, which sets the same fields.\n\nBoth then call serial8250_register_8250_port(), which does the\nfollowing:\n\n\tuart-\u003eport.iotype \u003d up-\u003eport.iotype;           /* UPIO_AU */\n\t...\n\tserial8250_set_defaults(uart);                 /* -\u003e set_io_from_upio(port) -\u003e default: WARN */\n\n\t/* Possibly override default I/O functions.  */\n\tif (up-\u003eport.serial_in)  uart-\u003eport.serial_in  \u003d up-\u003eport.serial_in;\n\tif (up-\u003eport.serial_out) uart-\u003eport.serial_out \u003d up-\u003eport.serial_out;\n\t...\n\tif (up-\u003edl_read)  uart-\u003edl_read  \u003d up-\u003edl_read;\n\tif (up-\u003edl_write) uart-\u003edl_write \u003d up-\u003edl_write;\n\nSo every UPIO_AU port goes through set_io_from_upio() before its own\naccessors are put back.\n\n3. History of set_io_from_upio()\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on the function gives this chain:\n\n - b6830f6df8914 (v4.3-rc1): Function moved into 8250_port.c with an\n   explicit \"case UPIO_AU:\" under CONFIG_SERIAL_8250_RT288X.\n\n - b334214ea08d9 (v6.5-rc1): Moved the AU code into the new\n8250_rt288x.c and deleted \"case UPIO_AU:\". AU now falls into default:.\n\n - 7c7e6c8924e7 (v6.13-rc1): default: becomes\n   WARN(1, \"Unsupported UART type %x\") + no_serial_in/out. AU now warns.\n\n - 4e450dfd0f968 (v6.13-rc3): \"Fixes: 7c7e6c8924e7\"; condition narrowed\n   to \"iotype !\u003d UPIO_PORT || iobase\". Still fires for iotype 4.\n\n - 49fa670efdfdd (next-20260519): The fix. Adds\n   \"case UPIO_AU: break;\".\n\nb334214ea08d9 (v6.5): a precondition, not the bug\n-------------------------------------------------\n\nBefore this commit the function was:\n\n\t#ifdef CONFIG_SERIAL_8250_RT288X\n\t\tcase UPIO_AU:\n\t\t\tp-\u003eserial_in \u003d au_serial_in; ... up-\u003edl_write \u003d au_serial_dl_write;\n\t\t\tbreak;\n\t#endif\n\t\tdefault:\n\t\t\tp-\u003eserial_in \u003d io_serial_in;\n\t\t\tp-\u003eserial_out \u003d io_serial_out;\n\t\t\tbreak;\n\nIlpo Järvinen moved the AU accessors into au_platform_setup() and\nrt288x_setup(), which hand them to the core through the\nup-\u003eport.serial_in and up-\u003edl_read override fields. He removed the\nUPIO_AU case at the same time.\n\nFrom v6.5 on, UPIO_AU fell into default:. That briefly assigned\nio_serial_in/out, and the override block then replaced them with the AU\naccessors. In that design default: was really the UPIO_PORT handler plus\na harmless fallback, so not recognising UPIO_AU had no visible effect:\nno warning and no functional problem.\n\n7c7e6c8924e7 (v6.13): where the bug starts\n------------------------------------------\n\nTo prepare for HAS_IOPORT\u003dn, which removes inb()/outb(), the commit\nchanged the switch like this:\n\n\t-\n\t-\tdefault:\n\t+#ifdef CONFIG_HAS_IOPORT\n\t+\tcase UPIO_PORT:\n\t \t\tp-\u003eserial_in \u003d io_serial_in;\n\t \t\tp-\u003eserial_out \u003d io_serial_out;\n\t \t\tbreak;\n\t+#endif\n\t+\tdefault:\n\t+\t\tWARN(1, \"Unsupported UART type %x\\n\", p-\u003eiotype);\n\t+\t\tp-\u003eserial_in \u003d no_serial_in;\n\t+\t\tp-\u003eserial_out \u003d no_serial_out;\n\nThis changes what default: means. It used to be the port-I/O fallback;\nnow it asserts that the iotype is unsupported. The commit listed only\nthe iotypes that had their own case (HUB6, MEM, MEM16, MEM32, MEM32BE,\nPORT). It did not account for UPIO_AU, which since v6.5 had relied on\nthe fallback, so every Alchemy and Ralink port registration started\nhitting WARN(1, ...). The warning is unconditional and independent of\nCONFIG_HAS_IOPORT.\n\nUpstream already treats this commit as the source of this exact\nwarning. Guenter Roeck\u0027s 4e450dfd0f968 carries \"Fixes: 7c7e6c8924e7\" for\nthe same \"Unsupported UART type\" backtrace with type 0, from\nuninitialised serial8250_ports[] entries. His fix only handled type 0\n(iotype !\u003d UPIO_PORT || iobase), so type 4 kept warning until\n49fa670efdfdd. The CVE fix closes the remaining gap that 7c7e6c8924e7\nopened.\n\n4. Impact\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - What breaks: a WARN with a full backtrace on every boot and every\n   port registration, on Alchemy Au1xxx and on Ralink/MediaTek SoCs\n   using \"ralink,rt2880-uart\". With panic_on_warn\u003d1 this becomes a\n   kernel panic at boot, which is a denial of service.\n\n - What doesn\u0027t break: I/O itself. default: sets no_serial_in/out, but\n   serial8250_register_8250_port() restores the AU accessors and the\n   AU dl_read/dl_write right after serial8250_set_defaults().\nset_io_from_upio() also records up-\u003ecur_iotype \u003d UPIO_AU, so the later\nre-checks (\"if (port-\u003eiotype !\u003d up-\u003ecur_iotype)\" at 8250_port.c:2284 and\n:3121) never call it again.\n\n - The fix is correct. \"case UPIO_AU: break;\" skips the WARN, and the\n   defaults it no longer assigns are overwritten anyway.\n\n5. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 7c7e6c8924e7. It made the default: branch warn without\n   handling UPIO_AU, which by then legitimately reached that branch.\n\n - Precondition only: b334214ea08d9 removed \"case UPIO_AU:\" but caused\n   no warning or malfunction on its own.\n\n - Affected range: v6.13-rc1 up to 49fa670efdfdd.\n\n - Suggested tag:\n\n\tFixes: 7c7e6c8924e7 (\"tty: serial: handle HAS_IOPORT dependencies\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "676a81a16254e43b1f45ce0cba77feee9b6afc3b",
      "tree": "0b038e2b49de78ba9fc79795e231d0c256f8d302",
      "parents": [
        "8bfcd757761a3a0c270d60c8c01fd1fd279c991c"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 11:01:21 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98882: Add .vulnerable file\n\nRoot cause: 45d1a2b93277a (\"media: chips-media: wave5: Add vpuapi\nlayer\"), by Nas Chung, 2023-11-08, first released in v6.8. That commit\nadded the driver, including the exact line the fix guards. No later\ncommit changed the line, the array size or the limits on the counts, and\nthe driver has no older copy in mainline (the old staging path has no\nhistory on origin/master).\n\n\tFixes: 45d1a2b93277 (\"media: chips-media: wave5: Add vpuapi layer\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore the fix (79aef69bb090, \"media: chips-media: wave5: Add range\nchecks for dec_output_info\"), wave5_vpu_dec_get_output_info() in\ndrivers/media/platform/chips-media/wave5/wave5-vpuapi.c did this:\n\n\tval \u003d p_dec_info-\u003enum_of_decoding_fbs; //fb_offset\n\n\tmax_dec_index \u003d (p_dec_info-\u003enum_of_decoding_fbs \u003e p_dec_info-\u003enum_of_display_fbs) ?\n\t\tp_dec_info-\u003enum_of_decoding_fbs : p_dec_info-\u003enum_of_display_fbs;\n\n\tif (info-\u003eindex_frame_display \u003e\u003d 0 \u0026\u0026\n\t    info-\u003eindex_frame_display \u003c (int)max_dec_index)\n\t\tinfo-\u003edisp_frame \u003d inst-\u003eframe_buf[val + info-\u003eindex_frame_display];\n\nThe fix computes idx \u003d val + index_frame_display and adds\nWARN_ON(idx \u003e\u003d MAX_REG_FRAME) before the read. If the check fires, it\nreturns -EINVAL through err_out, which also releases hw_lock. This\nmatches the smatch report quoted in the fix:\n\n\twave5-vpuapi.c:588 wave5_vpu_dec_get_output_info() error: buffer overflow \u0027inst-\u003eframe_buf\u0027 64 \u003c\u003d 127\n\n2. How the read works\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. The array. In wave5-vpuapi.h, struct vpu_instance has\n    \"struct frame_buffer frame_buf[MAX_REG_FRAME]\". MAX_REG_FRAME is\n    WAVE5_MAX_FBS * 2, and WAVE5_MAX_FBS is 32, so the array has 64\n    entries.\n\n 2. The layout. wave5_vpu_dec_register_frame_buffer_ex() splits the\n    array in two:\n     - Entries [0 .. num_of_decoding_fbs-1] hold the compressed (FBC)\n       reference frames.\n     - Entries from [num_of_decoding_fbs] onward hold the linear display\n       frames.\n    That is why the read adds val (the number of decoding buffers) to\n    the display index.\n\n 3. The display index comes from the hardware.\n    wave5_vpu_dec_get_result() in wave5-hw.c copies the raw\n    W5_RET_DEC_DISPLAY_INDEX register into result-\u003eindex_frame_display\n    without validating it. The firmware chooses this value, not the\n    driver.\n\n 4. The counts can hold values well above 64. struct dec_info declares\n    num_of_decoding_fbs and num_of_display_fbs as 7-bit bitfields, so\n    each can be 0 to 127. The sum used as the index can therefore reach\n    about 253 against a 64-entry array.\n\n 5. The local check has the wrong upper bound. The index is checked\n    against max(num_of_decoding_fbs, num_of_display_fbs):\n     - It is never checked against the real array size, MAX_REG_FRAME.\n     - It is not even checked against the size of the display region\n       it indexes (num_of_display_fbs). When there are more decoding\n       buffers than display buffers, an index can pass the check and\n       still land past the registered display buffers.\n\n 6. The only thing that keeps the read in bounds is in another\n    function. wave5_vpu_dec_register_frame_buffer_ex() rejects counts\n    of 32 or more, so in practice the largest index is 31 + 30 \u003d 61.\n    Nothing near the read enforces that, and the field types allow far\n    more. Because the safety depends on that unstated assumption,\n    smatch flags the line and the fix adds a check where the array is\n    read.\n\n 7. Effect. Indexing past frame_buf[63] reads a whole\n    struct frame_buffer (DMA addresses, sizes, stride and so on) from\n    the members after the array. Those are frame_vbuf[] and the rest of\n    vpu_instance, or memory beyond it. The value is copied into\n    info-\u003edisp_frame.\n     - The fix message says the driver \"can write in invalid memory\",\n       but the access is an out-of-bounds read from frame_buf. The only\n       write is to info-\u003edisp_frame, a struct the caller passed in, so\n       it stays in bounds.\n     - In the current tree, nothing in the V4L2 layer reads disp_frame.\n\n3. Why 45d1a2b93277a is the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - git blame on the fix\u0027s parent attributes every line involved (lines\n   578-595) to 45d1a2b93277a.\n\n - git log -S\"frame_buf[val + info-\u003eindex_frame_display]\" returns only\n   two commits: 45d1a2b93277a, which added the line, and the fix, which\n   removed it.\n\n - The other pieces all date from 45d1a2b93277a too:\n    - num_of_decoding_fbs and its 7-bit bitfield: -S finds only that\n      commit.\n    - WAVE5_MAX_FBS 32: -S finds only that commit.\n    - The \"\u003e\u003d WAVE5_MAX_FBS\" check: git log -L on\n      wave5_vpu_dec_register_frame_buffer_ex shows only that commit.\n    - MAX_REG_FRAME: the only other commits that touch it are the\n      V4L2-layer commit (9707a6254a8a6) and the fix, and neither\n      changes its value.\n\n - The later commits to these files don\u0027t touch this array access. The\n   closest is 4e4103d6f3e99 (\"Fix panic on decoding\nDECODED_IDX_FLAG_SKIP\"). It fixed a different problem in the same\nfunction: a negative disp_idx used to index dec_out_info[]. It did not\nchange the frame_buf[] access.\n\nThe driver went in through the v6.8 merge, so every release from v6.8\nup to the fix has this code.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "8bfcd757761a3a0c270d60c8c01fd1fd279c991c",
      "tree": "75c628ad57b7435350eadc910ba7ca153113294b",
      "parents": [
        "f65fa33795dae917fbfc524818f7df6d60c06cf9"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 10:36:01 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98881: Add .vulnerable file\n\nRoot cause: 19e3a9c90c534 (\"net: bridge: convert multicast to generic\nrhashtable\"), by Nikolay Aleksandrov, 2018-12-05, first released in\nv5.0-rc1. That commit turned a rare, conditional RCU barrier in\nbr_multicast_dev_del() into one that runs on every bridge deletion,\nwhile RTNL is held. git blame points at the next commit, 4329596cb10d2,\nbut that commit only renamed the call.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n25ae123db10ba removes the last statement of br_multicast_dev_del():\n\n\tbr_multicast_ctx_deinit(\u0026br-\u003emulticast_ctx);\n\tbr_multicast_gc(\u0026deleted_head);\n\tcancel_work_sync(\u0026br-\u003emcast_gc_work);\n  -\n  -\trcu_barrier();\n   }\n\nThe function runs from br_dev_uninit(), the bridge\u0027s .ndo_uninit\n(net/bridge/br_device.c:154). unregister_netdevice_many_notify() calls\nthat once per device, with RTNL held. The syzbot trace shows the netns\npath: cleanup_net -\u003e ops_undo_list -\u003e ops_exit_rtnl_list -\u003e\nunregister_netdevice_many_notify -\u003e br_dev_uninit -\u003e\nbr_multicast_dev_del -\u003e rcu_barrier -\u003e wait_for_completion.\n\nrcu_barrier() waits until every RCU callback already queued on every\nCPU has run. That means at least one full grace period plus draining\nall callback queues. It is also serialized system-wide by RCU\u0027s barrier\nmutex. Under load (syzbot, many queued callbacks) that can take a very\nlong time, and RTNL is held the whole time. When a netns teardown\nremoves several bridges, the barriers run back to back under the same\nRTNL hold. Every other RTNL user blocks behind it, which produces the\nhung-task reports.\n\nThe commit message says why removal is safe: the barrier dates from\nwhen br_multicast.c freed objects with call_rcu() and its own callbacks.\nToday the file frees everything with kfree_rcu() (current br_multicast.c\nlines 636, 685, 811 and 1494). There is no bridge-owned callback left to\nwait for, so the barrier only costs time.\n\n2. Tracing the line back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -G\u0027rcu_barrier\u0027 on net/bridge/ shows each stage.\n\nStage 0: a conditional barrier with a real purpose (2010 to 2018)\n-----------------------------------------------------------------\n\n - eb1d16414339a (Herbert Xu, v2.6.34-rc1, \"bridge: Add core IGMP\n   snooping support\") added the custom resizable MDB hash table\n(struct net_bridge_mdb_htable with a -\u003eold pointer). A rehash freed the\nold table with call_rcu_bh(\u0026mdb-\u003ercu, br_mdb_free), and br_mdb_free()\ncleared mdb-\u003eold. br_multicast_stop() contained:\n\n\tif (mdb-\u003eold) {\n\t\tspin_unlock_bh(\u0026br-\u003emulticast_lock);\n\t\tsynchronize_rcu_bh();\n\t\t...\n\n - 10cc2b50eb4b0 (\"bridge: Fix RCU race in br_multicast_stop\",\n   v2.6.34-rc2) changed that to rcu_barrier_bh(). Paul McKenney had\n   pointed out that only a barrier guarantees the pending br_mdb_free\n   callback has actually run.\n\n - e10177abf842 (2015, v4.3-rc1) moved this teardown code from\n   br_multicast_stop() into the new br_multicast_dev_del().\n\nJust before 19e3a9c90c534, the function looked like this:\n\n\tver \u003d mdb-\u003ever;\n\tfor (i \u003d 0; i \u003c mdb-\u003emax; i++)\n\t\thlist_for_each_entry_safe(mp, n, \u0026mdb-\u003emhash[i], hlist[ver]) {\n\t\t\tdel_timer(\u0026mp-\u003etimer);\n\t\t\tcall_rcu_bh(\u0026mp-\u003ercu, br_multicast_free_group);\n\t\t}\n\n\tif (mdb-\u003eold) {          /* only if a rehash is still in flight */\n\t\tspin_unlock_bh(\u0026br-\u003emulticast_lock);\n\t\trcu_barrier_bh();\n\t\tspin_lock_bh(\u0026br-\u003emulticast_lock);\n\t\tWARN_ON(mdb-\u003eold);\n\t}\n\n\tmdb-\u003eold \u003d mdb;\n\tcall_rcu_bh(\u0026mdb-\u003ercu, br_mdb_free);\n\nThe barrier had a correctness job: mdb-\u003eold was reused as the link for\nfreeing the table, so the previous rehash\u0027s callback had to finish\nfirst. It also ran only in the rare case where a rehash\u0027s RCU free was\nstill pending when the bridge was deleted. Normal bridge deletion never\nreached it.\n\nStage 1: the root cause, 19e3a9c90c534 (v5.0-rc1)\n-------------------------------------------------\n\nThis commit replaced the custom table with the generic rhashtable plus\na plain br-\u003emdb_list. That removed struct net_bridge_mdb_htable,\nmdb-\u003eold, br_mdb_rehash() and br_mdb_free(). The only thing the old\nbarrier protected was gone. Instead of dropping the barrier, the commit\nmoved it out of the if and made it unconditional:\n\n\t \tspin_lock_bh(\u0026br-\u003emulticast_lock);\n\t-\t...\n\t-\tif (mdb-\u003eold) {\n\t-\t\tspin_unlock_bh(\u0026br-\u003emulticast_lock);\n\t-\t\trcu_barrier_bh();\n\t-\t\tspin_lock_bh(\u0026br-\u003emulticast_lock);\n\t-\t\tWARN_ON(mdb-\u003eold);\n\t-\t}\n\t-\tmdb-\u003eold \u003d mdb;\n\t-\tcall_rcu_bh(\u0026mdb-\u003ercu, br_mdb_free);\n\t-out:\n\t+\thlist_for_each_entry_safe(mp, tmp, \u0026br-\u003emdb_list, mdb_node) {\n\t+\t\tdel_timer(\u0026mp-\u003etimer);\n\t+\t\trhashtable_remove_fast(\u0026br-\u003emdb_hash_tbl, \u0026mp-\u003erhnode,\n\t+\t\t\t\t       br_mdb_rht_params);\n\t+\t\thlist_del_rcu(\u0026mp-\u003emdb_node);\n\t+\t\tcall_rcu_bh(\u0026mp-\u003ercu, br_multicast_free_group);\n\t+\t}\n\t \tspin_unlock_bh(\u0026br-\u003emulticast_lock);\n\t+\n\t+\trcu_barrier_bh();\n\t }\n\nThree points show this is the bug the fix removes:\n\n 1. This barrier is the same rcu_barrier() the fix deletes. At this\n    commit, RCU flavor consolidation (v4.20) had already landed.\n    include/linux/rcupdate.h in this tree defines\n    \"static inline void rcu_barrier_bh(void) { rcu_barrier(); }\" under\n    \"Transitional pre-consolidation compatibility definitions\". So the\n    code already did a full, global rcu_barrier() on every bridge\n    unregistration.\n\n 2. The cost moved from rare to every call. Before, the barrier ran\n    only when a rehash\u0027s RCU free was pending. Afterwards every\n    \"ip link del\" of a bridge, and every bridge removed during netns\n    cleanup, did a full barrier inside .ndo_uninit with RTNL held.\n    Removing many bridges in one unregister batch pays it once per\n    bridge.\n\n 3. It was not needed even then. The only callbacks queued here were\n    br_multicast_free_group(), which just did kfree(mp) and touched no\n    bridge state. br_mdb_hash_fini() -\u003e rhashtable_destroy() runs\n    afterwards in br_dev_uninit(), but the entries were already removed\n    from the table. Module-unload safety was already covered by the\n    rcu_barrier() in br_deinit() (473c22d759e73, 2009), which runs\n    after unregister_pernet_subsys() has deleted every bridge. The\n    per-device barrier added no correctness, only RTNL hold time.\n\nStage 2: made obviously stale, 4329596cb10d2 (v5.0-rc1, next patch in\nthe same series)\n---------------------------------------------------------------------\n\n\"net: bridge: multicast: use non-bh rcu flavor\" deleted\nbr_multicast_free_group() and br_multicast_free_pg(). It converted every\ncall_rcu_bh(\u0026x-\u003ercu, cb) in br_multicast.c and br_mdb.c to kfree_rcu(x,\nrcu). In the same hunk it changed rcu_barrier_bh() to rcu_barrier().\nAfter this, git grep at 4329596cb10d2 finds no call_rcu left in\nbr_multicast.c or br_mdb.c; the only uses are kfree_rcu() and that\nrcu_barrier(). This is the \"stale\" state the fix message describes. It\nis also why git blame on the removed line names\n4329596cb10d2. That change was a rename to the same function, so it did\nnot introduce the behaviour.\n\nStage 3: later changes around the line (no change to the barrier)\n-----------------------------------------------------------------\n\n - 8b671779b7e2d (2020, group source lists) added\nHLIST_HEAD(deleted_head) and a src-gc cancel_work_sync() before the\nbarrier.\n\n - e12cec65b5546 (2020, \"destroy all entries via gc\") moved deletion\n   into br_multicast_del_mdb_entry() + br_multicast_gc(). Objects are\n   still freed with kfree_rcu() after del_timer_sync() in the\n   -\u003edestroy callbacks.\n\n - 613d61dbef8eb (2021) added br_multicast_ctx_deinit().\n\nNone of these changed the barrier. It sat unchanged until 25ae123db10ba\nremoved it.\n\n3. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 19e3a9c90c534, \"net: bridge: convert multicast to\n   generic rhashtable\" (v5.0-rc1). It made rcu_barrier_bh() (already\n   literally rcu_barrier()) unconditional in br_multicast_dev_del(), the\n   per-device .ndo_uninit path that runs under RTNL.\n\n - Made stale: 4329596cb10d2 (also v5.0-rc1, next patch in the series).\n   It moved everything to kfree_rcu() and renamed the call to\n   rcu_barrier(). Both commits shipped in v5.0-rc1, so the affected\n   range is the same either way.\n\n - Not the origin: 10cc2b50eb4b0 and eb1d16414339a. Their barrier was\n   conditional on mdb-\u003eold, rarely ran, and was needed for the old hash\n   table\u0027s RCU freeing.\n\n - Affected range: v5.0-rc1 up to the fix, 25ae123db10ba, which first\n   appears in v7.2-rc1.\n\nThe upstream thread for the fix (lore message-id\n20260519095540.2643318-1-edumazet@google.com) has only Reviewed-by and\nAcked-by replies from Jakub Sitnicki, Ido Schimmel and Nikolay\nAleksandrov. Nobody proposed a Fixes: tag, so the attribution above\ncomes from the git history alone.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "f65fa33795dae917fbfc524818f7df6d60c06cf9",
      "tree": "eab5c7b1e80129c0b2fb79ebf2bc4b1b5df8f01b",
      "parents": [
        "3dd75e7dbc8cead690eb7bf05ea45d2b290dc016"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 10:29:52 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98880: Add .vulnerable file\n\nRoot cause: 82cae269cfa95 (\"fs/ntfs3: Add initialization of super\nblock\"), by Konstantin Komarov, first in v5.15-rc1. It was the first\nntfs3 code to call sb_set_blocksize() and ignore its return value.\nEvery call the fix now checks is either that original call carried\nforward or a later copy of the same pattern.\n\nOne correction to the fix\u0027s commit message: on the tree the fix applies\nto, the BUG_ON() crash it describes can\u0027t happen through the two lines\nit changes. An earlier fix, d1693a7d5a38a, already blocks it.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n24f7d1824b758 is patch 9/10 of Christoph Hellwig\u0027s series \"fix crashes\nwhen mounting legacy file system with sector size \u003e PAGE_SIZE\" (merge\nd90e60ced4c3c, v7.2-rc1, no Fixes: tag). In ntfs_init_from_boot() it\nadds return-value checks to two calls:\n\n - Call A, fs/ntfs3/super.c:1177, on the main path:\n\n     sb_set_blocksize(sb, min_t(u32, sbi-\u003ecluster_size, PAGE_SIZE));\n\n - Call B, fs/ntfs3/super.c:1228, on the alternative-boot retry path:\n\n     sb_set_blocksize(sb, block_size);\n\nsb_set_blocksize() returns 0 on failure and leaves sb-\u003es_blocksize /\ns_blocksize_bits unchanged. That was already true when ntfs3 was merged.\nBack then set_blocksize() rejected sizes that weren\u0027t a power of two,\nwere outside 512..PAGE_SIZE, or were smaller than the device\u0027s logical\nblock size (bdev_logical_block_size, below just \"lbs\"). So a caller that\nignores the return keeps running with a block size it never asked for.\nEverything derived from it is then wrong: block_mask,\nblocks_per_cluster, volume.blocks, and every block number computed with\n\"lbo \u003e\u003e s_blocksize_bits\".\n\n2. History of the two lines\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L and git blame -w -M -C on the parent of the fix give this\nhistory:\n\n - 82cae269cfa95 (\"fs/ntfs3: Add initialization of super block\"),\n   v5.15-rc1: creates fs/ntfs3/super.c. Adds an unchecked\n\"sb_set_blocksize(sb, PAGE_SIZE)\" in ntfs_fill_super() before the boot\nsector is read. Adds an unchecked\n\"if (sbi-\u003ecluster_size \u003c PAGE_SIZE) sb_set_blocksize(sb,\nsbi-\u003ecluster_size);\" in ntfs_init_from_boot().\n\n - 28861e3bbd9e7 (\"fs/ntfs3: Initiliaze sb blocksize only in one place\n   + refactor\"), v5.15-rc6: only reshapes the second call into\n     \"sb_set_blocksize(sb, min_t(u32, sbi-\u003ecluster_size, PAGE_SIZE));\"\n   (call A, still unchecked) and drops the call in fill_super.\n\n - 09f7c338da781 (\"fs/ntfs3: Reject mount if boot\u0027s cluster size \u003c\n   media sector size\"), v5.15-rc6: carries \"Fixes: 82cae269cfa9\". Adds a\n   validation check that stops call A failing in the ordinary case, but\n   still leaves the return unchecked.\n\n - 6a4cd3ea7d771 (\"fs/ntfs3: Alternative boot if primary boot is\n   corrupted\"), v6.5-rc1: copies the pattern into the new retry path:\ncall B, also unchecked. (dcc852e509a4c and c39de951282df later changed\nonly neighbouring lines.)\n\n - d1693a7d5a38a (\"ntfs: set dummy blocksize to read boot_block when\n   mounting\"), v6.19-rc1: adds a *checked* sb_min_blocksize(sb,\n   PAGE_SIZE) before the first boot read (syzbot report).\n\n - 24f7d1824b758 (the fix), v7.2-rc1: finally checks calls A and B.\n\n3. How 82cae269cfa95 introduced the bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nOriginal code from 82cae269cfa95:\n\n    /* ntfs_fill_super() */\n    sb_set_blocksize(sb, PAGE_SIZE);\n    err \u003d ntfs_init_from_boot(sb, rq ? queue_logical_block_size(rq) : 512, ...);\n\n    /* ntfs_init_from_boot() */\n    /*\n     * - Volume formatted 512 and mounted as 4K\n     */\n    if (sbi-\u003esector_size !\u003d sector_size) { ntfs_warn(...); ... }   /* warns, continues */\n    ...\n    if (sbi-\u003ecluster_size \u003c sbi-\u003esector_size)   /* compares against the ON-DISK sector size only */\n            goto out;\n    ...\n    if (sbi-\u003ecluster_size \u003c PAGE_SIZE)\n            sb_set_blocksize(sb, sbi-\u003ecluster_size);     /* return ignored */\n\n    sbi-\u003eblock_mask         \u003d sb-\u003es_blocksize - 1;\n    sbi-\u003eblocks_per_cluster \u003d sbi-\u003ecluster_size \u003e\u003e sb-\u003es_blocksize_bits;\n    sbi-\u003evolume.blocks      \u003d sbi-\u003evolume.size \u003e\u003e sb-\u003es_blocksize_bits;\n\nThe code explicitly expects a \"volume formatted 512 and mounted as 4K\",\nbut nothing compared the cluster size with the device\u0027s lbs. With no\nrace and no malicious input, the bug triggers like this:\n\n 1. Setup: an NTFS image with 512 B, 1 KB or 2 KB clusters on a device\n    with 4 KB lbs. That could be a 4Kn disk,\n    \"losetup --sector-size 4096\", or a crafted boot sector.\n\n 2. Boot read succeeds: sb_set_blocksize(sb, 4096) works, and the boot\n    sector is read and validated.\n\n 3. The block-size change fails silently: sb_set_blocksize(sb, 512)\n    calls set_blocksize(), which returns -EINVAL because 512 \u003c lbs.\n    sb_set_blocksize() returns 0, ntfs3 ignores it, and s_blocksize\n    stays at 4096, which is larger than a cluster.\n\n 4. Every value derived from it is wrong:\n    blocks_per_cluster \u003d 512 \u003e\u003e 12 \u003d 0 and block_mask \u003d 4095. The mount\n    continues.\n\n 5. Data goes to the wrong place:\n\n     - ntfs_get_block() sets bh-\u003eb_blocknr \u003d lbo \u003e\u003e s_blocksize_bits for\n       a 4 KB buffer. That drops the cluster\u0027s offset inside the 4 KB\n       block.\n\n     - File reads return neighbouring clusters, including other files\u0027\n       data and metadata. Writes overwrite 7 unrelated clusters.\n\n     - In index.c, blen \u003d clen * blocks_per_cluster is 0, so bitmap\n       scans are skipped. ntfs_unmap_meta() processes zero blocks.\n\nKomarov\u0027s own follow-up confirms this. 09f7c338da781 is tagged\n\"Fixes: 82cae269cfa9\" and says \"If we continue to work in this case,\nthen we can corrupt fs\". Its new reject path carries the comment \"No way\nto use ntfs_get_block in this case\". It treated the symptom by rejecting\ncluster_size \u003c media sector size, which made call A unable to fail while\nlbs stays stable. It did not handle the return value, which is the\nactual missing piece that 24f7d1824b758 adds 4.5 years later.\n\n28861e3bbd9e7 is not the origin. It moved and reshaped the call but\nkept it unchecked. 6a4cd3ea7d771 is a second instance of the same\nmistake: when it added the alternative-boot retry, it called\nsb_set_blocksize(sb, min(sector_size, PAGE_SIZE)) without checking it.\n\n4. The BUG_ON() in the commit message\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe commit message is the same boilerplate used for all 10 patches. The\ncover letter says only \"roughly the first half\" of the filesystems\nactually crashed, and ntfs3 is patch 9/10. The crash it describes is the\ncase where s_blocksize \u003d\u003d 0 at the first __bread_gfp(). In that case,\n__bread_gfp() is called with size 0 and hits\nBUG_ON(offset \u003e\u003d folio_size(folio)) in folio_set_bh().\n\nThat requires two v6.15 changes: 47dd675323038 (allows block sizes up\nto 64K) and a64e5a596067b (sb_set_blocksize() rejects sizes above\nPAGE_SIZE without FS_LBS). With those, setup_bdev_super()\u0027s unchecked\nsb_set_blocksize(sb, block_size(bdev)) fails on a 64K-lbs device (or\nafter BLKBSZSET to 16K), leaving s_blocksize \u003d 0. These are enablers,\nnot the root cause. In v6.15-v6.18, the first ntfs_bread() crashed\nbefore either call A or call B ran.\n\nd1693a7d5a38a (v6.19-rc1) closed that path. On the fix\u0027s parent,\nfs/ntfs3/super.c:976 does:\n\n    if (!sb_min_blocksize(sb, PAGE_SIZE))\n            return -EINVAL;\n\nOn a 64K-lbs device, sb_min_blocksize() asks for 64K,\nsb_validate_large_blocksize() rejects it because ntfs3 doesn\u0027t set\nFS_LBS, and the mount fails cleanly. After that check succeeds, lbs is\nat most PAGE_SIZE. The cluster_size \u003e\u003d sector_size check (line 1116)\nthen means call A\u0027s size is at least lbs, and call B\u0027s size equals lbs.\n\nSo on the tree the fix was written against, calls A and B can only fail\nif lbs grows during the mount. Examples are an sd rescan, an NVMe\nreformat, or nbd (loop is blocked from this since 7e49538288e52, v6.17).\nBoth outcomes:\n\n - If call B fails, the retry reads the wrong block, ends up past the\n   end of the device, and the mount returns -EINVAL.\n\n - If call A fails, the mount continues with the same wrong geometry as\n   in 2021, where s_blocksize is larger than a cluster.\n\nThe patch is therefore mostly hardening that finishes off a bug from\nv5.15. Backporting it to v6.15-v6.18 does *not* fix the crash in its own\ncommit message; those trees need d1693a7d5a38a for that.\n\nConclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 82cae269cfa95 (\"fs/ntfs3: Add initialization of super\n   block\", v5.15-rc1), where ntfs3 began ignoring the documented failure\n   return of sb_set_blocksize(). The resulting fs-corruption bug was\n   already reachable in v5.15-rc1-rc5.\n\n - Same mistake on the second line (call B): 6a4cd3ea7d771 (v6.5-rc1).\n\n - Suggested tags:\n\n     Fixes: 82cae269cfa9 (\"fs/ntfs3: Add initialization of super block\")\n\n   plus optionally, for call B:\n\n     Fixes: 6a4cd3ea7d77 (\"fs/ntfs3: Alternative boot if primary boot is corrupted\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "3dd75e7dbc8cead690eb7bf05ea45d2b290dc016",
      "tree": "29fb496eeb418f9312cc2517442192bc4b30b413",
      "parents": [
        "2543ccda23e16f1fda1eb9430b0f91a417704297"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 10:20:36 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98879: Add .vulnerable file\n\nRoot cause: 1da177e4c3f41 (\"Linux-2.6.12-rc2\"). The unchecked\n\"sb_set_blocksize(s, orig_zonesize);\" call in isofs_fill_super() has\nbeen there since the first commit in git history, the 2.6.12-rc2 import.\nNothing touched that line until the fix.\n\nThe fix has no Fixes: tag. The tag it should carry is:\n\n  Fixes: 1da177e4c3f4 (\"Linux-2.6.12-rc2\")\n\nEvery kernel from v2.6.12 up to v7.2-rc1, where the fix landed, is\naffected.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n25ef4c4d9f0e9 (Christoph Hellwig, v7.2-rc1, no Fixes: tag) is patch\n7/10 of the series \"fix crashes when mounting legacy file system with\nsector size \u003e PAGE_SIZE\" (merge d90e60ced4c3c):\n\n    -\tsb_set_blocksize(s, orig_zonesize);\n    +\tif (!sb_set_blocksize(s, orig_zonesize))\n    +\t\tgoto out_freesbi;\n\nsb_set_blocksize() returns 0 on failure and leaves s-\u003es_blocksize and\ns_blocksize_bits unchanged. isofs ignored that return value. This call\nis supposed to switch the superblock\u0027s buffer size to the volume\u0027s\nlogical zone size. The comment above it says why that matters: if the\nblock size stays smaller than the zone size, \"we have horrible problems\nwith trying to piece together bits of adjacent blocks\". Ignoring the\nreturn value lets the mount continue without that guarantee.\n\n2. Tracing the line back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - git blame -w -M -C on the fix\u0027s parent: line 821\n   \"sb_set_blocksize(s, orig_zonesize);\" comes from ^1da177e4c3f41.\n\n - git log -L on that line: only 25ef4c4d9f0e9 (the fix) and\n   1da177e4c3f41.\n\n - git log -S\u0027sb_set_blocksize(s, orig_zonesize)\u0027: only 1da177e4c3f41.\n\n - git log -G\u0027orig_zonesize\u0027: also finds two commits that touched only\n   the neighbouring \"orig_zonesize \u003c opt.blocksize\" check, never the\n   call:\n\n     - c3ed85a36ff5b (v2.6.23, coding-style cleanup)\n     - 1b17a46c9243e (v6.10, mount API conversion, opt. -\u003e opt-\u003e)\n\nThe 2.6.12 helpers in fs/block_dev.c could already fail:\n\n    int set_blocksize(struct block_device *bdev, int size)\n    {\n    \tif (size \u003e PAGE_SIZE || size \u003c 512 || (size \u0026 (size-1)))\n    \t\treturn -EINVAL;\n    \tif (size \u003c bdev_hardsect_size(bdev))\n    \t\treturn -EINVAL;\n    \t...\n    int sb_set_blocksize(struct super_block *sb, int size)\n    {\n    \tif (set_blocksize(sb-\u003es_bdev, size))\n    \t\treturn 0;          /* s_blocksize left untouched */\n\nSo the missing check was a bug from day one. Later rewrites of\nsb_set_blocksize() (38885bd4c2a4b, ead083aeeed9d, 47dd675323038,\na64e5a596067b, 50b2a4f19b224) all kept the \"return 0, don\u0027t touch\ns_blocksize\" contract. isofs code predates git, so there is no earlier\ncommit to point at.\n\n3. How the call can actually fail in isofs\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix\u0027s commit message is boilerplate shared across the series. It\ndescribes BUG_ON(offset \u003e\u003d folio_size(folio)) in folio_set_bh(), which\nneeds s_blocksize \u003d\u003d 0: a size-0 sb_bread() passes the alignment check,\nand the loop in folio_alloc_buffers() never moves offset. That path\ncan\u0027t be reached through this isofs line, which is consistent with the\ncover letter\u0027s \"the first half crashed, the rest I fixed for the\npattern\". Here is what is guaranteed before the call at line 821:\n\n 1. Large sectors are rejected early. 09a4e0be5826a (v4.19) refuses\n    devices with a logical block size above 2048 before any read:\n    \"ISOFS: unsupported/invalid hardware sector size\". Hellwig\u0027s 64K\n    loop device is rejected cleanly here.\n\n 2. s_blocksize is already non-zero.\n    \"opt-\u003eblocksize \u003d sb_min_blocksize(s, opt-\u003eblocksize)\" sets it to\n    max(option, device sector size). Both are at most 2048, so the call\n    succeeds. Its result has also been checked since e106e269c5cb3\n    (v6.18-rc7).\n\n 3. The zone size is valid. orig_zonesize comes from the on-disk\n    descriptor and is limited to 512, 1024 or 2048 by the switch. It is\n    also at least opt-\u003eblocksize, otherwise the mount bails out through\n    out_bad_size.\n\nGiven those, sb_set_blocksize(s, orig_zonesize) can only fail if the\ndevice\u0027s sector size grows between sb_min_blocksize() and line 821.\nThat is a time-of-check/time-of-use race, and the window includes the\nvolume-descriptor sb_bread() scan (slow on optical drives). Ways to grow\nit:\n\n - CD-ROM media change: another opener triggers sr_block_open(), then\n   disk_check_media_change(), then sr_revalidate_disk(), which commits a\n   new logical_block_size (512 \u003c-\u003e 2048).\n\n - SCSI disk rescan: an sd rescan can change the sector size.\n\n - Loop devices: LOOP_SET_BLOCK_SIZE on a loop device being mounted,\n   until 7e49538288e52 (v6.17) made it claim the device exclusively.\n\nFor example: orig_zonesize is 2048, opt-\u003eblocksize is 1024, and the\nsector size jumps to 4096. The 2048 request fails, s_blocksize stays\n1024, and isofs carries on using zone numbers (2048-byte units) as\n1024-byte buffer block numbers. That is the wrong-geometry state the\nin-code comment warns about.\n\nWhat happens next on current kernels: __getblk_slow() hits\nWARN_ON_ONCE(!IS_ALIGNED(size, bdev_logical_block_size(bdev))) and\nreturns NULL. Before 77eb64439ad52 (v6.17-rc1) it printed an error\ninstead. Either way the root-inode read fails and the mount fails.\nStale cached buffers are the right size, so nothing reads out of\nbounds. The worst outcome is a WARN, which crashes the machine only with\npanic_on_warn. For isofs, this CVE fixes missing error handling and is\nhardening rather than a reachable crash.\n\n4. Commits that are not the root cause\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 1b17a46c9243e (mount API conversion), v6.10: carried the line\n   unchanged. e106e269c5cb3 blamed it for the sibling sb_min_blocksize()\n   check, a different call.\n\n - 09a4e0be5826a (reject sector size \u003e 2048), v4.19: a mitigation.\n   It makes the commit-message BUG path unreachable for isofs.\n\n - e106e269c5cb3 (check sb_min_blocksize()), v6.18-rc7: same bug class,\n   but a different call.\n\n - 47dd675323038, a64e5a596067b (64K block sizes; reject block sizes\n   above PAGE_SIZE), v6.15: enabled the BUG path for the other\n   filesystems in the series. isofs stops at the 2048 check first.\n\n - 77eb64439ad52 (removed size limits in __getblk_slow), v6.17-rc1:\nturned a size-0 read into a BUG for the siblings; here it only changes\nprintk to WARN.\n\n - 7e49538288e52 (loop: no block-size change under an exclusive owner),\n   v6.17: narrowed one way to trigger the race.\n\nThis matches the earlier analyses of the sibling CVEs from the same\nseries. hpfs, jfs, qnx4, bfs and affs (CVE-2026-97486..90) all trace to\n1da177e4c3f41; omfs (CVE-2026-97485) traces to 555e3775ced1d. The\ndifference for isofs is that the commit message\u0027s BUG path doesn\u0027t\napply.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "2543ccda23e16f1fda1eb9430b0f91a417704297",
      "tree": "221e5c10e5f608b14dea3d4c5cf86b95485ffb36",
      "parents": [
        "e9a3aee0ee72d606b9c192b4d5d573573204c0fb"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 10:13:01 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:14 2026 -0400"
      },
      "message": "CVE-2026-98878: Add .vulnerable file\n\nRoot cause: 7ec80fb3f0258 (\"irqchip/gic-v5: Add GICv5 PPI support\"), by\nLorenzo Pieralisi, first released in v6.17-rc1. This is the commit that\ncreated the GICv5 host driver, and it put the priority drop\n(GIC CDEOI) in irq_eoi() instead of right after the interrupt is\nacknowledged.\n\nThe fix has no Fixes: tag. The tag it should carry is:\n\n  Fixes: 7ec80fb3f025 (\"irqchip/gic-v5: Add GICv5 PPI support\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nabf60331ebe9a (\"irqchip/gic-v5: Immediately exec priority drop\nfollowing activate\"), v7.2-rc1, is patch 17/18 of Marc Zyngier\u0027s\n\"KVM: arm64: vgic-v5 fixes for 7.2\" series. It was authored by Sascha\nBischoff with Suggested-by: Marc Zyngier. It moves one instruction:\n\n - Removed: gic_insn(0, CDEOI) from gicv5_hwirq_eoi(). This helper\n   backs the .irq_eoi callbacks for PPIs, SPIs and LPIs/IPIs, and is\n   also used on the \"Could not handle\" path.\n\n - Removed: the CDEOI in the forwarded-to-vCPU branch of\n   gicv5_ppi_irq_eoi().\n\n - Added: gic_insn(0, CDEOI) in gicv5_handle_irq(), right after CDIA\n   (acknowledge), gsb_ack() and isb().\n\n     static void gicv5_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)\n     ...\n     \tgic_insn(cddi, CDDI);\n    -\n    -\tgic_insn(0, CDEOI);\n     }\n\n     static void gicv5_ppi_irq_eoi(struct irq_data *d)\n     {\n     \t/* Skip deactivate for forwarded PPI interrupts */\n    -\tif (irqd_is_forwarded_to_vcpu(d)) {\n    -\t\tgic_insn(0, CDEOI);\n    +\tif (irqd_is_forwarded_to_vcpu(d))\n     \t\treturn;\n    -\t}\n     ...\n     static void __exception_irq_entry gicv5_handle_irq(struct pt_regs *regs)\n     ...\n     \tisb();\n\n    +\tgic_insn(0, CDEOI);\n    +\n     \thwirq \u003d FIELD_GET(GICV5_HWIRQ_INTID, ia);\n\nAfter the fix, deactivation (CDDI) still happens in irq_eoi(), so the\nsame interrupt cannot fire again until software deactivates it. Only the\npriority drop moves earlier.\n\n2. Why the old placement is a bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nGICv5 splits the end of an interrupt into three separate instructions:\n\n  GICR CDIA   Acknowledges the interrupt: makes it active and raises\n              the CPU\u0027s running priority to its priority.\n  GIC CDEOI   Priority drop. It names no interrupt; it just lowers the\n              highest active running priority.\n  GIC CDDI    Deactivates one specific interrupt (by ID and type).\n\nUntil CDEOI runs, the CPU interface will not signal any interrupt of\nequal or lower priority. The driver gives every interrupt the same\npriority, GICV5_IRQ_PRI_MI (gicv5_ppi_priority_init() and the\nIRS/SPI/LPI equivalents). So after an acknowledge, nothing else can be\nsignalled to that CPU until CDEOI runs. Before the fix, that only\nhappened when the irq core called irq_eoi().\n\nCompare GICv3. Whenever the kernel boots at EL2 (the only setup where\nKVM is possible), GICv3 uses split EOI/Deactivate mode\n(supports_deactivate_key):\n\n - gic_complete_ack() in drivers/irqchip/irq-gic-v3.c writes\nICC_EOIR1_EL1 (the priority drop) right after the acknowledge, in\n__gic_handle_irq().\n\n - .irq_eoi \u003d gic_eoimode1_eoi_irq only deactivates, and skips\n   interrupts forwarded to a vCPU.\n\nSo on GICv3, \"skipping irq_eoi\" or \"leaving an interrupt active for a\nlong time\" never leaves the running priority raised. The GICv5 driver\nkept the drop inside irq_eoi, which is effectively GICv3\u0027s non-split\n(EOImode\u003d0) behaviour. Marc\u0027s suggested fix brings GICv5 in line with\nsplit mode.\n\n3. Where the bug was introduced\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit blame on the parent of the fix (abf60331ebe9a^) attributes both\nhalves to 7ec80fb3f0258:\n\n    /* drivers/irqchip/irq-gic-v5.c @ abf60331ebe9a^, lines 213-223 */\n    static void gicv5_hwirq_eoi(u32 hwirq_id, u8 hwirq_type)\n    {\n    \t...\n    \tgic_insn(cddi, CDDI);\n\n    \tgic_insn(0, CDEOI);    /* priority drop coupled to deactivate */\n    }\n\n    /* gicv5_handle_irq(), lines 942-969 */\n    \tia \u003d gicr_insn(CDIA);  /* activate + raise running priority */\n    \t...\n    \tgsb_ack();\n    \tisb();\n    \thwirq \u003d FIELD_GET(GICV5_HWIRQ_INTID, ia);\n    \thandle_irq_per_domain(hwirq);  /* no CDEOI here: drop waits\n    \t                                  for irq_eoi */\n\n7ec80fb3f0258 also set up the conditions that make this harmful:\n\n - It wired the helper in as .irq_eoi \u003d gicv5_ppi_irq_eoi.\n\n - It programmed every PPI to the single priority GICV5_IRQ_PRI_MI.\n\n - It reused gicv5_hwirq_eoi() (with CDEOI inside) on the \"Could not\n   handle\" path.\n\nThe bug is in code this commit added, not in an older refactor, so it\ncannot be traced back any further.\n\n4. Later commits that spread or worked around the design\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThese came later and are not the origin:\n\n - 5cb1b6dab2def (\"irqchip/gic-v5: Add GICv5 IRS/SPI support\") and\n   0f01013258768 (\"irqchip/gic-v5: Add GICv5 LPI/IPI support\"), both\n   v6.17-rc1: gicv5_spi_irq_eoi() and gicv5_lpi_irq_eoi() call the same\n   helper, and the IPI and ITS chips (0f01013258768, 57d72196dfc85\n   (\"irqchip/gic-v5: Add GICv5 ITS support\")) use irq_chip_eoi_parent.\n   That extended the deferred drop to every interrupt type.\n\n - 244e9a89ca765 (\"irqchip/gic-v5: Skip deactivate for forwarded PPI\n   interrupts\"), v6.17-rc1, for GICv3-compat guests: for forwarded PPIs\n   it skips CDDI but keeps CDEOI inside irq_eoi. It says it \"mimics the\n   behaviour seen on native GICv3\", but it does not: on GICv3 the drop\n   already happened at acknowledge. It also made a forwarded PPI\u0027s\n   priority drop depend on irq_eoi actually being called. The fix\n   removes this branch too, but this commit only applied 7ec80fb3f0258\u0027s\n   design to a new path.\n\n - 9491c63b6cd7 (\"KVM: arm64: gic-v5: Enlighten arch timer for GICv5\"),\n   v7.1-rc1: KVM\u0027s stacked timer irqchip followed GICv3 rules and did\n   not call the parent EOI for forwarded interrupts. On GICv5 that means\n   the priority never drops, so the commit forced irq_chip_eoi_parent()\n   on GICv5 hosts. Its comment says: \"This is required to emulate the\nEOIMode\u003d1 on GICv5 hardware. Failure to call EOI unsurprisingly results\nin *BAD* lock-ups.\" That is a workaround for the root cause, and it\nconfirms it.\n\n - bee399ea20c8f (\"KVM: arm64: Fix arch timer interrupts for\n   GICv3-on-GICv5 guests\"), v7.2-rc1, patch 18/18 and the companion fix:\n   removes that workaround, so forwarded timer interrupts no longer call\n   the parent EOI. This is only safe because abf60331ebe9a now drops\n   priority at acknowledge.\n\n5. How the denial of service happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nFor host-only interrupts, the window is usually short:\n\n - The flow handlers (handle_percpu_devid_irq, handle_fasteoi_irq,\n   handle_percpu_irq) call irq_eoi in the same hard-IRQ context.\n\n - No GICv5 chip sets IRQCHIP_EOI_THREADED or IRQCHIP_EOI_IF_HANDLED,\n   both of which would defer or skip EOI. This was checked across the\n   chips in irq-gic-v5*.c at abf60331ebe9a^.\n\nThe dangerous case is anything that runs with physical interrupts\ndeliverable between CDIA and irq_eoi, or that skips the parent irq_eoi.\nThe typical example is a KVM guest after a physical interrupt is\nforwarded or injected in software:\n\n 1. The host acknowledges the interrupt and its running priority rises.\n\n 2. A stacked chip, or the forwarding logic, treats \"skip irq_eoi\" as\n    \"skip deactivate\" (the GICv3 meaning), so CDEOI never runs.\n\n 3. The CPU enters the guest with the physical running priority still\n    raised.\n\n 4. The host timer, IPIs and device interrupts are all at the same\n    priority, so none of them can be signalled. The host loses control\n    of that CPU (no scheduler tick, no IPIs, RCU stalls), which is the\n    \"denial of service for all other interrupts\" in the changelog.\n\n6. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Introduced: v6.17-rc1 (7ec80fb3f0258). 244e9a89ca765 landed in the\n   same release, so the range is the same whichever of the two is named.\n\n - Fixed: v7.2-rc1 (abf60331ebe9a). The CVE announcement also lists\n   6.18.53 (c29bb5acd0cad).\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e9a3aee0ee72d606b9c192b4d5d573573204c0fb",
      "tree": "041bae84ece53237b8e07a7298f2ee8b4c4f885f",
      "parents": [
        "a31eb8a3002a6f0b3232bfc1dab194680ca7276f"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 10:01:06 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98877: Add .vulnerable file\n\nVulnerable since: a75460547e13c (\"KVM: arm/arm64: GICv4: Enable VLPI\nsupport\"), first released in v4.15-rc1. This is the first commit at\nwhich the stale has_v4 advertisement that the fix corrects can trigger\nthe crash, so it is what .vulnerable records. The same oops was already\nreachable a few commits earlier through an unrelated missing check.\nThat window never reached a tagged release and does not change the\naffected range (see section 5).\n\nDefect origin: 4bdf502517288 (\"irqchip/gic-v3: Advertise GICv4 support\nto KVM\"), v4.14-rc1. That commit started advertising\ngic_kvm_info::has_v4 from a flag that is wrong when no ITS is probed.\nIn v4.14, though, nothing reads has_v4 and nothing calls\nits_alloc_vcpu_irqs(), so v4.14 is not affected (see section 5).\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ne61654fbc3bc5 (Mostafa Saleh, merged for v7.2-rc1, no Fixes: tag) adds\none line to the early \"no ITS\" return in its_init():\n\n    \tif (list_empty(\u0026its_nodes)) {\n    +\t\trdists-\u003ehas_vlpis \u003d false;\n    \t\tpr_warn(\"ITS: No ITS available, not enabling LPIs\\n\");\n    \t\treturn -ENXIO;\n    \t}\n\nThe lore thread explains the choice. v1, \"Harden against bogus command\nline\", added \"if (!gic_domain) return -EINVAL;\" in\nits_alloc_vcpu_irqs(). Marc Zyngier rejected that as \"a good few levels\ntoo late\". He said the real problem is that gic_kvm_info::has_v4 itself\nhas to be reliable, and he wrote this one-liner. On v2 he renamed the\nsubject to \"Don\u0027t advertise VLPIs if no ITS is probed\" because that\n\"actually describes the nature of the issue\".\n\nSo the defect is not the NULL pointer itself. The defect is that the\nGIC driver tells KVM \"GICv4 is available\" when no ITS was probed, so the\nGICv4 layer was never set up.\n\n2. How the crash happens (current tree)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. gic_init_bases() (drivers/irqchip/irq-gic-v3.c:2025) sets\n    gic_data.rdists.has_vlpis \u003d true by default.\n\n 2. __gic_update_rdist_properties() (irq-gic-v3.c:1091) does\n    \"has_vlpis \u0026\u003d GICR_TYPER.VLPIS\" for each redistributor. On GICv4\n    hardware it stays true. At this point the flag only means \"the\n    redistributors can handle VLPIs\". It says nothing about an ITS.\n\n 3. its_init() (irq-gic-v3-its.c): if the DT has no msi-controller ITS\n    node (or ACPI has no ITS entry), its_nodes is empty and the function\n    returns -ENXIO. That return happens before the block that would\n    call its_init_v4():\n\n        if (has_v4 \u0026 rdists-\u003ehas_vlpis) {\n                if (its_init_vpe_domain() ||\n                    its_init_v4(parent_domain, \u0026its_vpe_domain_ops, sgi_ops)) {\n                        rdists-\u003ehas_vlpis \u003d false; ...\n\n    As a result gic_domain, vpe_domain_ops and sgi_domain_ops in\n    irq-gic-v4.c stay NULL, but has_vlpis is still true.\n\n 4. gic_of_setup_kvm_info() / gic_acpi_setup_kvm_info()\n    (irq-gic-v3.c:2173 and :2539) run after gic_init_bases() and copy\n    the stale value into the KVM contract:\n\n        gic_v3_kvm_info.has_v4 \u003d gic_data.rdists.has_vlpis;\n\n 5. vgic_v3_probe() (arch/arm64/kvm/vgic/vgic-v3.c:968) does\n    \"if (info-\u003ehas_v4) has_gicv4 \u003d gicv4_enable;\". With\n    kvm-arm.vgic_v4_enable\u003d1, KVM now believes GICv4 works.\n\n 6. A VM with a virtual ITS makes vgic_supports_direct_msis() true.\n    Then vgic_init() (or vgic_its_create() if the vgic is already\n    initialized) calls vgic_v4_init(), which calls\n    its_alloc_vcpu_irqs(), which calls:\n\n        irq_domain_create_hierarchy(gic_domain /*NULL*/, ...,\n                                    vpe_domain_ops /*NULL*/, vm)\n\n 7. The fault: __irq_domain_instantiate() checks for a NULL parent, so\n    gic_domain is not what faults. The inlined __irq_domain_create() -\u003e\n    irq_domain_check_hierarchy() does \"if (domain-\u003eops-\u003ealloc)\". In\n    struct irq_domain_ops the field order is match, select, map, unmap,\n    xlate, alloc, which puts alloc at offset 0x28. That matches the\n    report\u0027s \"NULL pointer dereference at virtual address\n    0000000000000028\" with pc in __irq_domain_instantiate. Both pointers\n    are NULL for the same reason: its_init_v4() never ran.\n\nThe trigger is an admin opt-in plus GICv4 hardware without an ITS.\nAfter that, any process with /dev/kvm access that creates a VM with a\nvITS crashes the host.\n\n3. Tracing the history\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 0edc23ea2692f (\"irqchip/gic-v3: Add VLPI/DirectLPI discovery\"),\n   v4.14-rc1.\nWhat it did: added rdists.has_vlpis, defaulting to true and ANDed with\nGICR_TYPER.VLPIS.\nRole: creates the flag. It correctly describes redistributor\ncapability. Its only user was a pr_info.\n\n - 8fff27aec182e (\"irqchip/gic-v3-its: Add VPE domain infrastructure\"),\n   v4.14-rc1.\n   What it did: added\n\"if (has_v4 \u0026 has_vlpis) { if (its_init_vpe_domain()) has_vlpis \u003d\nfalse; }\" after the existing list_empty(\u0026its_nodes) early return.\nRole: starts clearing the flag on GICv4 init failure, but only on paths\nthat have an ITS.\n\n - 7de5c0af9c7c7 (\"irqchip/gic-v4: Add per-VM VPE domain creation\"),\n   v4.14-rc1.\nWhat it did: added gic_domain, vpe_domain_ops and\nits_alloc_vcpu_irqs().\n   Role: the function that later faults.\n\n - 3d63cb53e221d (\"irqchip/gic-v4: Enable low-level GICv4 operations\"),\n   v4.14-rc1.\nWhat it did: added its_init_v4(), the only place gic_domain and\nvpe_domain_ops get set, inside that ITS-only block.\n   Role: from here, has_vlpis can be true while gic_domain is NULL.\n\n - 4bdf502517288 (\"irqchip/gic-v3: Advertise GICv4 support to KVM\"),\n   v4.14-rc1.\n   What it did: added\n\"gic_v3_kvm_info.has_v4 \u003d gic_data.rdists.has_vlpis;\" to both the DT\nand ACPI paths.\nRole: DEFECT ORIGIN. A raw capability bit becomes the \"GICv4 is usable\"\npromise to KVM.\n\n - 74fe55dc9ab77 (\"KVM: arm/arm64: GICv4: Add init/teardown of the\n   per-VM vPE irq domain\"), v4.15-rc1.\n   What it did: added vgic_v4_init() -\u003e its_alloc_vcpu_irqs().\nRole: consumer. Its call from vgic_its_create() was not gated on GICv4\nsupport at all (see section 5).\n\n - a75460547e13c (\"KVM: arm/arm64: GICv4: Enable VLPI support\"),\n   v4.15-rc1.\nWhat it did: added kvm-arm.vgic_v4_enable and \"if (info-\u003ehas_v4)\nhas_gicv4 \u003d gicv4_enable;\".\nRole: makes the bug reachable. This is the only commit that ever sets\nhas_gicv4 true for GICv3. RECORDED IN .vulnerable.\n\n - 3d1ad640f8c94 (\"KVM: arm/arm64: Fix GICv4 ITS initialization\n   issues\"), v4.15-rc1, two commits after a75460547e13c.\nWhat it did: moved the vgic_supports_direct_msis() check into\nvgic_v4_init() itself.\nRole: closes the unrelated, ungated vgic_its_create() path described in\nsection 5. From here on, every path into its_alloc_vcpu_irqs() needs\nhas_gicv4.\n\n - f8f85dc00b742 (\"KVM: arm64: Fix GICv4 init when called from\n   vgic_its_create\"), v4.15-rc9.\nWhat it did: changed that check to \"if\n(!kvm_vgic_global_state.has_gicv4) return 0;\".\nRole: keeps every path into its_alloc_vcpu_irqs() dependent on\nhas_gicv4, which only a75460547e13c\u0027s has_v4 check sets to true.\n\n4. Why the defect originates in 4bdf502517288\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nJust before 4bdf502517288, \"git grep has_vlpis\" finds only the default,\nthe AND with the TYPER bit, the pr_info, and the its_init() block that\nruns only when an ITS exists. With no ITS, its_cpu_init() skips\neverything (\"if (!list_empty(\u0026its_nodes))\") and gic_rdists is not even\nassigned. So a stale true in the no-ITS case had no functional effect.\nIt only produced a misleading \"VLPI support\" log line.\n\n4bdf502517288 exported that flag as gic_kvm_info::has_v4. That is the\n\"advertising\" the fix\u0027s title refers to. It assumed has_vlpis meant \"the\nITS and the GICv4 layer are initialized\", which is only true when an\nITS was probed. The fix leaves this assignment in place and corrects its\ninput, which is exactly Marc\u0027s \"make gic_kvm_info::has_v4 reliable\".\n\nWhy the defect does not originate in the other commits:\n\n - 0edc23ea2692f measures a real hardware property correctly. Nothing\n   used it for anything except logging.\n\n - 8fff27aec182e / 3d63cb53e221d left the no-ITS path untouched.\nNothing outside its_init() read the flag yet, so that gap was harmless.\n\n - a75460547e13c is a correct consumer of a contract the irqchip side\n   broke. The fix rightly touches only the producer.\n\nAll the irqchip commits landed in the same v4.14-rc1 GICv4 merge, so\npicking any of them gives the same range: the code is present from\nv4.14, and the crash is reachable from v4.15 (when\nkvm-arm.vgic_v4_enable appeared).\n\n5. Why .vulnerable records a75460547e13c\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe bug needs two pieces: the irqchip side advertising has_v4 while the\nGICv4 layer is not set up (4bdf502517288, v4.14-rc1), and KVM acting on\nhas_v4 (a75460547e13c, v4.15-rc1). .vulnerable records the point at\nwhich the kernel became vulnerable, which is when the second piece\nlanded. Recording 4bdf502517288 would mark v4.14 as affected, even\nthough nothing in v4.14 can reach the faulting code:\n\n - v4.14: has_v4 is declared in include/linux/irqchip/arm-gic-common.h\n   and written at irq-gic-v3.c:1175 and :1469, but nothing reads it.\nits_alloc_vcpu_irqs() is defined in irq-gic-v4.c and declared in\narm-gic-v4.h, but has no callers. There is no vgic_v4_init() and no\nhas_gicv4.\n\n - 74fe55dc9ab77 (v4.15-rc1) added vgic_v4_init() and two callers. The\n   call from vgic_init() is gated by vgic_supports_direct_msis(), which\n   returns \"kvm_vgic_global_state.has_gicv4 \u0026\u0026 vgic_has_its(kvm)\". At\n   a75460547e13c^ nothing assigns has_gicv4, so it stays false and this\n   call never happens. The call from vgic_its_create() is gated only by\n   vgic_initialized(), and vgic_v4_init() has no GICv4 check of its own.\n   That path is a separate bug, covered below.\n\n - a75460547e13c added to vgic_v3_probe():\n\n        if (info-\u003ehas_v4) {\n                kvm_vgic_global_state.has_gicv4 \u003d gicv4_enable;\n\nIts changelog says \"All it takes is the has_v4 flag to be set in\ngic_kvm_info as well as \"kvm-arm.vgic_v4_enable\u003d1\" being passed on the\ncommand line\". At this commit the whole chain from section 2 is present.\nits_init() still returns -ENXIO at \"if (list_empty(\u0026its_nodes))\", before\nthe\n\"if (has_v4 \u0026 rdists-\u003ehas_vlpis)\" block. gic_domain and vpe_domain_ops\nare assigned only in its_init_v4() (irq-gic-v4.c:224 and :225).\nits_alloc_vcpu_irqs() passes them to irq_domain_create_hierarchy().\n__irq_domain_add() calls irq_domain_check_hierarchy(), which does \"if\n(domain-\u003eops-\u003ealloc)\".\nalloc comes after match, select, map, unmap and xlate in struct\nirq_domain_ops, at offset 0x28.\n\nThe earlier, unrelated crash (74fe55dc9ab77 up to 3d1ad640f8c94^):\n\nFrom 74fe55dc9ab77 until 3d1ad640f8c94, vgic_its_create() did\n\n        if (vgic_initialized(dev-\u003ekvm)) {\n                int ret \u003d vgic_v4_init(dev-\u003ekvm);\n\nand vgic_v4_init() went straight from \"if (dist-\u003eits_vm.vpes) return\n0;\" to its_alloc_vcpu_irqs(). The vITS device is registered on every\nvgic-v3 host: kvm_register_vgic_device() calls\nkvm_vgic_register_its_device() for KVM_DEV_TYPE_ARM_VGIC_V3. So on any\nhost where its_init_v4() never ran, creating a vITS device after\nKVM_DEV_ARM_VGIC_CTRL_INIT reached irq_domain_create_hierarchy() with\nNULL gic_domain and vpe_domain_ops. It faulted at the same\n\"if (domain-\u003eops-\u003ealloc)\" (offset 0x28) in irq_domain_check_hierarchy().\nThat covers every GICv3 host without GICv4, as well as this CVE\u0027s\nGICv4-without-ITS setup.\n\nThat window does not move the boundary to 74fe55dc9ab77:\n\n - It is a different bug. It needs neither the stale has_v4 nor\n   kvm-arm.vgic_v4_enable, and has_gicv4 is never true at those commits.\n   The cause is the missing GICv4 check in vgic_v4_init(), not what the\n   irqchip advertises. Applying e61654fbc3bc5 on top of 74fe55dc9ab77\n   would not stop it. 3d1ad640f8c94 fixed it by moving the\n   vgic_supports_direct_msis() check into vgic_v4_init() (\"We should\n   only try to initialize GICv4 data structures on a GICv4 capable\n   system. [...] so that any KVM VGIC initialization path does not fail\n   on non-GICv4 systems\"). f8f85dc00b742 (v4.15-rc9) then changed it to\n   a has_gicv4 check. Since 3d1ad640f8c94, every path into\n   its_alloc_vcpu_irqs() needs has_gicv4. KVM sets has_gicv4 to true\n   only from has_v4, in vgic_v3_probe(); vgic-v5.c only clears it.\n   That is the path the CVE fix closes.\n\n - It never reached a tagged release. 74fe55dc9ab77, a75460547e13c and\n   3d1ad640f8c94 all first appear in v4.15-rc1, and 3d1ad640f8c94 is two\n   commits after a75460547e13c. Recording 74fe55dc9ab77 instead would\n   give exactly the same affected range.\n\nSo the affected range starts at v4.15-rc1.\n\n6. Related paths the fix doesn\u0027t cover\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThese are outside this CVE\u0027s fix, but have the same shape:\n\n - irqchip.gicv3_nolpi\u003d1 (f736d65df0ace, v4.17):\ngic_dist_supports_lpis() returns false, so its_init() is never called\nand has_vlpis is still advertised.\n\n - ITS present, but none is GICv4-capable:\n\"if (has_v4 \u0026 rdists-\u003ehas_vlpis)\" has no else, so the flag stays true\nand gic_domain stays NULL.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "a31eb8a3002a6f0b3232bfc1dab194680ca7276f",
      "tree": "3b28eb1561b4b2df11d1008b1c53c8c279559915",
      "parents": [
        "e40e03f266e9cb74c026c42a31da36cd91aa516a"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 09:18:10 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98876: Add .vulnerable file\n\nRoot cause: 8f7da1578e90 (\"gpu: host1x: Cleanup on initialization\nfailure\"), by Thierry Reding, first released in v4.16-rc1.\n\n933deb8c7b8e (\"gpu: host1x: Add early init and late exit callbacks\"),\nv5.13-rc1, later copied the same mistake into a second error-unwind\nloop. That is the fix\u0027s second hunk.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nFix 71d25f668bc5 (v7.2-rc1, no Fixes: tag) changes two lines in\nhost1x_device_init() in drivers/gpu/host1x/bus.c:\n\n     teardown:\n     \tlist_for_each_entry_continue_reverse(client, \u0026device-\u003eclients, list)\n    -\t\tif (client-\u003eops-\u003eexit)\n    +\t\tif (client-\u003eops \u0026\u0026 client-\u003eops-\u003eexit)\n     \t\t\tclient-\u003eops-\u003eexit(client);\n     ...\n     teardown_late:\n     \tlist_for_each_entry_continue_reverse(client, \u0026device-\u003eclients, list)\n    -\t\tif (client-\u003eops-\u003elate_exit)\n    +\t\tif (client-\u003eops \u0026\u0026 client-\u003eops-\u003elate_exit)\n     \t\t\tclient-\u003eops-\u003elate_exit(client);\n\nIn host1x, struct host1x_client::ops is optional. Every other use in\nbus.c checks \"client-\u003eops \u0026\u0026\" first: the early_init and init loops, both\nloops in host1x_device_exit(), and suspend/resume. Only these two\nerror-unwind loops read client-\u003eops-\u003eX with no check. A client with ops\n\u003d\u003d NULL therefore causes a NULL dereference, at a small offset into the\nfunction-pointer table, the first time host1x_device_init() takes an\nerror path.\n\n2. Where the \"ops may be NULL\" rule comes from\n   \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Before host1x was split out, the Tegra DRM driver\u0027s own client loop\n   already used \"if (client-\u003eops \u0026\u0026 client-\u003eops-\u003einit)\" and \"if\n   (client-\u003eops \u0026\u0026 client-\u003eops-\u003eexit)\".\n\n - 776dc3840367 (\"drm/tegra: Move subdevice infrastructure to host1x\"),\n   v3.13-rc1, moved this code into drivers/gpu/host1x/bus.c as\n   host1x_device_init() and host1x_device_exit(), keeping the check in\n   both.\n\n - At that point the error path inside host1x_device_init() only did\n\"mutex_unlock(); return err;\". It never touched client-\u003eops, so there\nwas no bug yet.\n\n3. Root cause: 8f7da1578e90 (v4.16-rc1)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThis commit added unwinding when a client\u0027s init fails, instead of just\nreturning:\n\n     \t\t\t\tdev_err(\u0026device-\u003edev, \"failed to initialize %s: %d\\n\", ...);\n    -\t\t\t\tmutex_unlock(\u0026device-\u003eclients_lock);\n    -\t\t\t\treturn err;\n    +\t\t\t\tgoto teardown;\n     ...\n    +teardown:\n    +\tlist_for_each_entry_continue_reverse(client, \u0026device-\u003eclients, list)\n    +\t\tif (client-\u003eops-\u003eexit)\n    +\t\t\tclient-\u003eops-\u003eexit(client);\n    +\n    +\tmutex_unlock(\u0026device-\u003eclients_lock);\n    +\treturn err;\n\nThe forward loop it unwinds is\n\"list_for_each_entry(...) if (client-\u003eops \u0026\u0026 client-\u003eops-\u003einit)\". That\nloop deliberately walks past clients whose ops is NULL. The new teardown\nloop walks back over exactly the same clients (every client before the\nfailing one), but drops the \"client-\u003eops \u0026\u0026\" check. That is the defect\nthe CVE fix repairs.\n\nHow it triggers:\n\n 1. A host1x logical device has clients in list order: client A with\n    ops \u003d\u003d NULL, then client B with a valid init.\n\n 2. The driver\u0027s probe calls host1x_device_init(). In-tree callers are\n    drivers/gpu/drm/tegra/drm.c,\n    drivers/staging/media/tegra-video/video.c and\n    drivers/crypto/tegra/tegra-se-main.c.\n\n 3. The init loop skips A (NULL ops) and calls B-\u003eops-\u003einit(), which\n    fails (for example -EPROBE_DEFER, -ENOMEM, or a clock/reset error).\n\n 4. The code jumps to teardown:. The reverse walk starting before B\n    reaches A and evaluates A-\u003eops-\u003eexit, which reads address\n    NULL + offsetof(exit) (0x8 on 64-bit at the time) and oopses inside\n    probe.\n\n\"git blame 71d25f668bc5c^ -- drivers/gpu/host1x/bus.c\" attributes the\nteardown: label, the loop and the \"if (client-\u003eops-\u003eexit)\" line to\n8f7da1578e90. Nothing touched them between that commit and the fix.\n\n4. Second introduction: 933deb8c7b8e (v5.13-rc1)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n\"gpu: host1x: Add early init and late exit callbacks\" added an\nearly_init pass and a teardown_late: label. The new forward loop and the\nnew loop in host1x_device_exit() both have the \"client-\u003eops \u0026\u0026\" check.\nThe new error-unwind loop copied the unchecked style from 8f7da1578e90:\n\n    +\t/* reset client to end of list for late teardown */\n    +\tclient \u003d list_entry(\u0026device-\u003eclients, struct host1x_client, list);\n    +\n    +teardown_late:\n    +\tlist_for_each_entry_continue_reverse(client, \u0026device-\u003eclients, list)\n    +\t\tif (client-\u003eops-\u003elate_exit)\n    +\t\t\tclient-\u003eops-\u003elate_exit(client);\n\nThis made the bug reachable in more ways:\n\n - An early_init failure now jumps straight to teardown_late and\n   dereferences ops on every earlier client.\n\n - After an init failure, the exit teardown falls through. client is\n   reset to the list head, so the late_exit loop dereferences ops on\n   every client in the list, including ones after the failing client.\n   A NULL-ops client anywhere in the list now crashes.\n\nIt does not create a new kind of bug. It repeats the 8f7da1578e90\nmistake for a new callback, so it is a propagation rather than the\norigin.\n\n5. Practical exposure\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nOn origin/master, every in-tree client sets .ops before calling\nhost1x_client_register(). That covers the Tegra DRM clients (dc, dsi,\nhdmi, sor, hub, gr2d, gr3d, vic, nvdec, nvjpg), tegra-video (vi, csi,\nvip) and tegra-se. Registration does not require ops, and the rest of\nthe code treats NULL as allowed. So the bug is a latent NULL dereference\nin the probe error path: it needs a client without ops, such as an\nout-of-tree or future driver, plus an init/early_init failure on a later\nclient.\n\n6. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Original root cause: 8f7da1578e90 (\"gpu: host1x: Cleanup on\n   initialization failure\"), v4.16-rc1. It added the unchecked\n   client-\u003eops-\u003eexit dereference that the fix\u0027s first hunk guards. The\n   suggested Fixes: tag is:\n\n       Fixes: 8f7da1578e90 (\"gpu: host1x: Cleanup on initialization failure\")\n\n - Same bug copied: 933deb8c7b8e (\"gpu: host1x: Add early init and late\n   exit callbacks\"), v5.13-rc1, added the unchecked late_exit\n   dereference (second hunk). A complete pair of tags would list it too.\n\n - Not responsible: 776dc3840367 (v3.13) had the correct checks and no\n   unwind code.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e40e03f266e9cb74c026c42a31da36cd91aa516a",
      "tree": "eca8a4f6a20693455da840dc280b132d78bcb230",
      "parents": [
        "6e1d72fe43becb27b5964546ebbc8235d459645b"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 09:13:23 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98875: Add .vulnerable file\n\nRoot cause: 176fdcbddfd2 (\"drm/nouveau/gsp/r535: add support for\nbooting GSP-RM\"), by Ben Skeggs, first released in v6.7-rc1.\n\nThis is the GA100 SEC2 NULL dereference. 176fdcbddfd2 gave GA100 the\nGSP-RM boot path, which uses the SEC2 falcon. It added SEC2 to the new\nAda chip tables but never to nv170_chipset (GA100). So on GA100\ndevice-\u003esec2 was NULL from the first moment the booter path could run.\n\nThe fix has no Fixes: tag; the right one is:\n\n    Fixes: 176fdcbddfd2 (\"drm/nouveau/gsp/r535: add support for booting GSP-RM\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nFix 604d0efb17cc (\"drm/nouveau/gsp: add SEC2 to GA100 chip table\"),\nTimur Tabi, v7.2-rc1. It is patch 2 of a 10-patch GA100 series and has\nno Fixes: tag. It adds one line to the GA100 chip table in\ndrivers/gpu/drm/nouveau/nvkm/engine/device/base.c:\n\n     nv170_chipset \u003d {\n     \t.name \u003d \"GA100\",\n     \t...\n     \t.gsp      \u003d { 0x00000001, ga100_gsp_new },\n     \t...\n     \t.fifo     \u003d { 0x00000001, ga100_fifo_new },\n    +\t.sec2     \u003d { 0x00000001, tu102_sec2_new },\n     };\n\ndevice-\u003esec2 is only created if the chip table has a .sec2 entry.\nGA100\u0027s table never had one, yet GA100\u0027s GSP code needs SEC2.\n\n2. How the crash happens (code just before the fix)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. ga100_gsp (nvkm/subdev/gsp/ga100.c) boots GSP-RM the Turing way:\n     - .booter.ctor \u003d tu102_gsp_booter_ctor\n     - .oneinit \u003d tu102_gsp_oneinit\n     - .init \u003d tu102_gsp_init\n\n 2. tu102_gsp_oneinit() builds both booter firmwares against the SEC2\n    falcon:\n\n        ret \u003d gsp-\u003efunc-\u003ebooter.ctor(gsp, \"booter-load\", gsp-\u003efws.booter.load,\n                                     \u0026device-\u003esec2-\u003efalcon, \u0026gsp-\u003ebooter.load);\n        ret \u003d gsp-\u003efunc-\u003ebooter.ctor(gsp, \"booter-unload\", gsp-\u003efws.booter.unload,\n                                     \u0026device-\u003esec2-\u003efalcon, \u0026gsp-\u003ebooter.unload);\n\n 3. With device-\u003esec2 \u003d\u003d NULL, \u0026device-\u003esec2-\u003efalcon is not NULL. It\n    evaluates to offsetof(struct nvkm_sec2, falcon), a small bogus\n    address, because falcon comes after func and engine in the struct.\n\n 4. tu102_gsp_booter_ctor() passes that pointer to\n    nvkm_falcon_fw_ctor(). Its check\n\n        return falcon ? nvkm_falcon_fw_oneinit(fw, falcon, NULL, NULL) : 0;\n\n    passes, so nvkm_falcon_fw_oneinit() stores fw-\u003efalcon \u003d falcon.\n    Booter blobs never set fw-\u003eboot, so nothing touches the pointer\n    yet.\n\n 5. The fault comes when GSP-RM is started:\n     - tu102_gsp_init() -\u003e tu102_gsp_booter_load() -\u003e\n       nvkm_falcon_fw_boot(\u0026gsp-\u003ebooter.load, ...)\n     - -\u003e nvkm_falcon_get(falcon, user) -\u003e mutex_lock(\u0026falcon-\u003emutex)\n       on the bogus near-NULL address\n     - -\u003e kernel oops during GSP-RM boot, as the fix commit describes.\n\nOn all other GSP-RM chips this works because their tables have SEC2:\nTU10x (nv162-nv168) use tu102_sec2_new, GA10x (nv172-nv177) use\nga102_sec2_new. GA100 was the only GSP-RM chip without it.\n\n3. History of the nv170_chipset table\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on nv170_chipset, plus the GA100 GSP file, shows these steps:\n\n - 3b050680c841 (\"drm/nouveau/core: recognise GA10[024]\"), v5.11-rc4:\n   Adds an empty nv170_chipset and hides GA100 behind\n   NvEnableUnsupportedChipsets. No GSP, no SEC2 use.\n\n - 015ef6187f69 (\"drm/nouveau/gsp: prepare for GSP-RM\"), v6.7-rc1:\nAdds .gsp \u003d ga100_gsp_new, but ga100_gsps[] only has gv100_gsp_nofw. No\nbooter, no SEC2 use, so not the bug yet.\n\n - 176fdcbddfd2 (\"drm/nouveau/gsp/r535: add support for booting\nGSP-RM\"), v6.7-rc1: Adds the GA100 GSP-RM entry (535.54.03, booter\nctor, tu102_gsp_oneinit). The booter ctor is called from\nr535_gsp_oneinit() with \u0026device-\u003esec2-\u003efalcon. Adds .sec2 to the five\nnew Ada tables in the same diff, but not to nv170_chipset.\n   This is where the bug starts.\n\n - b5bad8c16b9b, 7f022236b55b, 594766ca3e53, 53dac0623853, ...\n   (v6.7 to v6.16): Firmware version bumps and refactoring.\n594766ca3e53 (v6.16-rc1) only moves the two booter-ctor calls from\nr535_gsp_oneinit() into tu102_gsp_oneinit(). The bad dereference is\nunchanged.\n\n - 20e0c197802c (\"drm/nouveau/gsp: add support for GA100\"), v7.1-rc1:\n   Adds .sec2 \u003d tu102_sec2_new (fixing this by accident) and makes\n   GA100 supported without the option.\n\n - c9e3878ae2f5 (\"Revert \u0027drm/nouveau/gsp: add support for GA100\u0027\"),\n   v7.1-rc3: Patch 1 of the series. Removes .sec2 again and puts GA100\n   back behind NvEnableUnsupportedChipsets. This restores the state\n   176fdcbddfd2 created, so v7.1 final shipped without SEC2.\n\n - 604d0efb17cc (the fix), v7.2-rc1: Adds .sec2 back.\n\nChecked per release (SEC2 entries in nv170_chipset / booter.ctor in\ngsp/ga100.c):\n\n    Release            SEC2 entry   Booter path   Result\n    ----------------   ----------   -----------   ------------\n    v6.6               none         none          not affected\n    v6.7, v7.0, v7.1   none         present       bug present\n    v7.2               present      present       fixed\n\n4. Why 176fdcbddfd2 and not a later commit\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - 176fdcbddfd2 is the commit that made GA100 depend on SEC2. Before\n   it, GA100 had no booter path, so a missing SEC2 entry did no harm.\nIt added a GA100 GSP-RM entry that runs booter-load/unload on SEC2,\nwithout the matching .sec2 entry. The same diff gave the new Ada tables\n.sec2 \u003d ga102_sec2_new, and the existing TU10x/GA10x tables already had\nSEC2, so the author knew the booter needs it. GA100 was simply left out.\n\n - 594766ca3e53 only moved code; the bad pointer is passed exactly as\n   before.\n\n - 20e0c197802c removed the bug for a while (v7.1-rc1/rc2).\n\n - c9e3878ae2f5 brought it back by reverting to the 176fdcbddfd2 state.\n   It explains why v7.1 final is affected, but it did not originate the\n   bug. 604d0efb17cc was sent in the same series to restore SEC2 on top\n   of the revert.\n\n5. Who can hit it (affected: v6.7 through v7.1.y)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAll three conditions must hold:\n\n 1. GA100 must be enabled with\n    nouveau.config\u003dNvEnableUnsupportedChipsets\u003d1. This gate from\n    3b050680c841 applies across the whole range; 20e0c197802c\u0027s\n    removal of it was reverted.\n\n 2. The GSP-RM path must be chosen:\n     - v6.7: needs NvGspRm\u003d1.\n     - From 1d492944d3d0 (v6.8-rc6): CONFIG_DRM_NOUVEAU_GSP_DEFAULT\n       turns it on by default.\n     - From e0ed674acbac (v6.18-rc1): on by default for\n       Turing/Ampere.\n\n 3. The nvidia/ga100/gsp/* firmware must be installed.\n\nOnce all three hold, the result is a kernel oops at probe or GSP boot.\nLater in the series, f0de0f89cc1e and 61de054a772a make GA100\nofficially supported (GSP-RM required), which needs this fix in place\nfirst.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "6e1d72fe43becb27b5964546ebbc8235d459645b",
      "tree": "e78d0d11fc3c61f14afc2bd5162e69c6fc9955d1",
      "parents": [
        "62e4340020cce9f6dbd1413448c05f1c5b797dd0"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 09:05:51 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98874: Add .vulnerable file\n\nRoot cause: d1e879ec600f9 (\"wifi: iwlwifi: add iwlmld sub-driver\"), by\nMiri Korenblit, 2025-02-16, first released in v6.15-rc1.\n\nThe bad WARN_ON() was in iwl_mld_wowlan_suspend() from the day the\niwlmld driver was added. No later commit introduced or moved it. The fix\nhas no Fixes: tag; the right one is:\n\n    Fixes: d1e879ec600f (\"wifi: iwlwifi: add iwlmld sub-driver\")\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nFix 301b0dfa9db3f (Johannes Berg, v7.2-rc1) edits\niwl_mld_wowlan_suspend() in drivers/net/wireless/intel/iwlwifi/mld/d3.c:\n\n         if (!bss_vif-\u003ecfg.assoc) {\n                 int ret;\n    -            /* If we\u0027re not associated, this must be netdetect */\n    -            if (WARN_ON(!wowlan-\u003end_config))\n    +            /*\n    +             * If not associated we can only do netdetect, if\n    +             * that\u0027s not enabled then just suspend normally.\n    +             */\n    +            if (!wowlan-\u003end_config)\n                         return 1;\n\nThe return value stays the same (1, meaning \"I can\u0027t do WoWLAN here,\ndisconnect and suspend normally\"). Only the WARN_ON() goes. So the bug\nwas never about wrong behaviour. The driver asserted that a perfectly\nvalid user configuration could not happen:\n\n - WoWLAN is configured,\n - a station interface is up but not associated, and\n - no net-detect trigger is set.\n\n2. Why that state is reachable\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe driver\u0027s comment \"If we\u0027re not associated, this must be netdetect\"\nwas false. Nothing above the driver filters on association or\nnet-detect:\n\n 1. nl80211 / WoWLAN config. nl80211_set_wowlan() fills\n    wowlan-\u003end_config only when NL80211_WOWLAN_TRIG_NET_DETECT is\n    given. If the user enables only magic-packet, disconnect, EAP or\n    rfkill-release, wowlan_config is non-NULL and nd_config is NULL.\n    iwlmld advertised all of these in d1e879ec600f9\n    (WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT | ... |\n    WIPHY_WOWLAN_NET_DETECT, set up in mld/mac80211.c).\n\n 2. cfg80211 wiphy_suspend() (net/wireless/sysfs.c, same logic in v6.15\n    and on master). If rdev-\u003ewiphy.wowlan_config is set, it calls\n    rdev_suspend(rdev, wowlan_config) without checking association.\n    Only if the driver returns 1 does it run cfg80211_leave_all() and\n    call rdev_suspend(rdev, NULL).\n\n 3. mac80211 __ieee80211_suspend() (net/mac80211/pm.c):\n     - It skips the driver only if !local-\u003eopen_count, so an interface\n       that is up but disconnected still gets through.\n     - It sets local-\u003ewowlan \u003d wowlan.\n     - It runs ieee80211_mgd_quiesce(), which aborts any auth/assoc in\n       progress. That makes vif-\u003ecfg.assoc \u003d\u003d false even more likely at\n       this point.\n     - It calls drv_suspend(local, wowlan). A return of 1 is the\n       documented \"refuse WoWLAN\" signal: WARN_ON(err !\u003d 1) plus the\n       comment \"cfg80211 will call back into mac80211 to disconnect all\n       interfaces\".\n\n 4. iwlmld:\n     - iwl_mld_suspend() (mld/mac80211.c) calls\n       iwl_mld_wowlan_suspend().\n     - iwl_mld_get_bss_vif() (mld/iface.c) returns the single STA vif\n       known to the firmware. iwl_mld_get_fw_id_bss_bitmap_iter keys\n       only on iftype, and the MAC is added to the firmware at\n       add_interface time, independent of association. So bss_vif is\n       non-NULL with cfg.assoc \u003d\u003d false.\n     - The code then hits WARN_ON(!wowlan-\u003end_config).\n\nConcrete trigger: BE200 or newer with .97+ firmware (so iwlmld loads),\nwlan0 up but not connected (out of range, just disconnected, or\nmid-association), then \"iw phy phy0 wowlan enable magic-packet\", then\nsuspend (e.g. closing the lid). The result is a kernel WARN splat on\nevery such suspend. With panic_on_warn\u003d1, the machine panics on suspend,\nwhich is a DoS.\n\n3. Why d1e879ec600f9 is the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBlame and pickaxe evidence:\n\n - git blame 301b0dfa9db3f^ on the WARN line and the comment above it\n   points to d1e879ec600f9.\n - git log -S\u0027WARN_ON(!wowlan-\u003end_config)\u0027 finds only d1e879ec600f9 and\n   the fix.\n - git log -L on iwl_mld_wowlan_suspend() shows only three commits:\nd1e879ec600f9 (created it with the WARN), b753b3334bad7 (sibling fix,\nbelow) and the CVE fix.\n - mld/d3.c itself was created by d1e879ec600f9.\n\nd1e879ec600f9 also made the code live. The same commit changed\nKconfig, Makefile, iwl-drv.c, cfg/bz.c and pcie/drv.c so that iwlmld is\nselected for BE200+ with .97 firmware. That means the bug was reachable\nfrom the first release that contained it, v6.15.\n\nThis was not inherited from iwlmvm. iwlmld reimplemented mvm\u0027s suspend\nflow, and mvm never warned here:\n\n - 8ca151b568b67 (original MVM driver, v3.9-rc1):\n   iwl_mvm_d3_iface_iterator() skipped unassociated STA vifs\n   (ap_sta_id \u003d\u003d IWL_MVM_STATION_COUNT). That gave\n!suspend_iter_data.vif, so it set \"ret \u003d 1; goto out_noreset;\" with no\nWARN.\n\n - 1e4d19ca6efba, then 3acc952b03765 (\"refactor wowlan and netdetect\n   configuration when suspending\", v3.19-rc1): this is where the comment\n   \"if we\u0027re not associated, this must be netdetect\" first appears. Even\n   there the code handled the missing case gracefully:\n\n       if (!wowlan-\u003end_config \u0026\u0026 !mvm-\u003end_config) {\n               ret \u003d 1;\n               goto out_noreset;\n       }\n\n - Up to and including the tree at d1e879ec600f9, mvm\u0027s\n   __iwl_mvm_suspend() still read\n   \"if (!wowlan-\u003end_config) { ret \u003d 1; goto out_noreset; }\", with no\n   WARN.\n\nWhen the iwlmld authors ported this flow, they kept mvm\u0027s comment, took\nit literally, and turned mvm\u0027s graceful \"ret \u003d 1\" fallback into \"if\n(WARN_ON(!wowlan-\u003end_config)) return 1;\". That conversion of a normal,\nuser-reachable branch into an assertion is the root cause, and it\nhappened in d1e879ec600f9.\n\n4. Related commit (same root, different bug)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nb753b3334bad7 (\"wifi: iwlwifi: mld: don\u0027t WARN on WoWLAN suspend w/o\nBSS vif\", v7.1-rc5) removed the neighbouring WARN_ON(!bss_vif) in the\nsame function. That is the case where WoWLAN is configured but no STA\ninterface exists at all. It also came from d1e879ec600f9 and is the\ncompanion of this CVE. It did not touch the nd_config WARN, which stayed\nuntil 301b0dfa9db3f.\n\n5. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nAny tree containing d1e879ec600f9 (v6.15-rc1 and later) but not\n301b0dfa9db3f (fixed in v7.2-rc1). Only iwlmld is affected (BE200+ with\n.97+ firmware). The iwlmvm path has never had this WARN.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "62e4340020cce9f6dbd1413448c05f1c5b797dd0",
      "tree": "2ec388a2c9937a7a6cb0dc199d3dca1768e388b6",
      "parents": [
        "7dae72e1e533407530b9f7f6e2b1597979de2786"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:59:01 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98873: Add .vulnerable file\n\nRoot cause: 076a0ca02644 (\"netfilter: ctnetlink: add NAT support for\nexpectations\"), by Pablo Neira Ayuso, first released in v3.4.\n\n076a0ca02644 was the first code to send an expectation\u0027s NAT fields\n(saved_ip/saved_proto/dir) to userspace. Nothing guaranteed those\nfields were initialized, and the decision to emit them looks at whether\nthe possibly-garbage values are non-zero.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n241ccd2fed90 (Florian Westphal, landed in v7.2-rc2, no Fixes: tag) does\nthree things:\n\n - nf_ct_expect_alloc(): kmem_cache_alloc() becomes\n   kmem_cache_zalloc(), so every expectation starts fully zeroed.\n - nf_ct_expect_init(): adds the missing exp-\u003edir \u003d 0, next to the\n   existing memsets of saved_addr/saved_proto.\n - ctnetlink_alloc_expect(): removes the hand-written else branches\n   that zeroed flags, expectfn, saved_addr, saved_proto and dir, because\n   zalloc now covers them.\n\nThe fix\u0027s commit message only says \"occasional LLM hints wrt. leaking\nuninitialized data to userspace via ctnetlink\". The concrete leak is\ndescribed below.\n\n2. The leak mechanism\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nstruct nf_conntrack_expect has a NAT-only tail:\n\n    #if IS_ENABLED(CONFIG_NF_NAT)\n            union nf_inet_addr saved_addr;\n            union nf_conntrack_man_proto saved_proto;\n            enum ip_conntrack_dir dir;\n    #endif\n\nctnetlink_exp_dump_expect() decides whether to emit CTA_EXPECT_NAT by\nchecking whether those fields are non-zero:\n\n            if (!nf_inet_addr_cmp(\u0026exp-\u003esaved_addr, \u0026any_addr) ||\n                exp-\u003esaved_proto.all) {\n                    ...\n                    if (nla_put_be32(skb, CTA_EXPECT_NAT_DIR, htonl(exp-\u003edir)))\n                    ...\n                    nat_tuple.src.u3 \u003d exp-\u003esaved_addr;\n                    nat_tuple.src.u \u003d exp-\u003esaved_proto;\n                    ctnetlink_exp_dump_tuple(skb, \u0026nat_tuple, CTA_EXPECT_NAT_TUPLE)\n\nThis only works if every creator of an expectation zeroes those fields.\nBefore the fix, the allocator did not zero anything\n(kmem_cache_alloc(nf_ct_expect_cachep, GFP_ATOMIC)). Any creator that\nskipped them left stale data from the slab object\u0027s previous occupant.\nThat data:\n\n - makes the non-zero check pass, so a bogus CTA_EXPECT_NAT is emitted;\n - is then copied out as raw bytes. For IPv4 that is 4 bytes of dir,\n4 bytes of saved_addr and 2 bytes of saved_proto (the port) per\nexpectation.\n\nThe leak goes out both on dumps (IPCTNL_MSG_EXP_GET, e.g.\n\"conntrack -L expect\") and on IPEXP_NEW event messages, which\nnf_ct_expect_related() sends immediately.\n\nWhich creators were exposed, and when\n-------------------------------------\n\n - nf_ct_expect_init() users (ftp, irc, sip, h323, pptp, amanda, tftp,\n   sane, IPVS, nft_ct): NAT fields not initialized from v3.4 to\n   v3.11-rc3. Zeroed by f09eca8db018 (v3.11-rc4), except dir.\n - ctnetlink_alloc_expect() (ctnetlink create and nfqueue glue) without\n   CTA_EXPECT_NAT: NAT fields not initialized from v3.4 to v7.0-rc6.\n   Zeroed by 35177c687713 (v7.0-rc7).\n - nf_conntrack_broadcast_help() (netbios-ns and snmp helpers): NAT\n   fields NEVER initialized. It fills tuple, master_tuple, mask, flags,\n   class, expectfn, helper, assign_helper, net and zone by hand, without\n   nf_ct_expect_init(), and never touches saved_*/dir.\n\nSo just before 241ccd2fed90, the broadcast helper was the last\nreachable leaking path. To hit it:\n\n - CONFIG_NF_NAT is enabled;\n - the netbios-ns (UDP 137) or snmp (UDP 161) helper is attached to a\n   connection;\n - a locally generated packet in the original direction is sent to an\n   interface\u0027s broadcast address;\n - a process with CAP_NET_ADMIN in that network namespace dumps\n   expectations or listens for expectation events.\n\nThe zalloc closes that path, and any other creator that might forget a\nfield. The dir \u003d 0 added to nf_ct_expect_init() is hygiene rather than a\nreachable leak. dir is only dumped when saved_* are non-zero, init\nzeroes those, and every NAT helper that sets saved_* also sets dir\n(checked in nf_nat_ftp, irc, amanda, tftp, sip, h323 and pptp).\n\n3. History, step by step\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. f587de0e2feb9 / 77ab9cff0f411 (v2.6.20 era): add or move saved_ip,\n    saved_proto and dir into the expectation struct. At this point only\n    NAT expectfn callbacks read them, and only on expectations whose\n    helper had just set them. Nothing was exported, so this was\n    harmless.\n\n 2. 92703eee4ccde (v2.6.20, NetBIOS-NS helper), later made generic as\n    nf_conntrack_broadcast_help() by 93557f53e1fbd (v2.6.39). It builds\n    expectations by hand and ignores the NAT fields. That was harmless\n    too: its expectfn is NULL, so no reader ever looked at them, and\n    /proc/net/nf_conntrack_expect (exp_seq_show) has never printed\n    them.\n\n 3. 076a0ca02644 (Feb 2012, v3.4-rc1), the root cause. It adds the\n    CTA_EXPECT_NAT dump shown in section 2 (then\n    \"if (exp-\u003esaved_ip || exp-\u003esaved_proto.all)\" plus\n    \"NLA_PUT_BE32(... htonl(exp-\u003edir))\") and the parser\n    ctnetlink_parse_expect_nat(). At that moment:\n\n     - nf_ct_expect_alloc() did not zero;\n     - nf_ct_expect_init() did not touch the NAT fields (checked in the\n       tree at that commit);\n     - the new create path only set them\n       \"if (cda[CTA_EXPECT_NAT])\", with no else;\n     - the broadcast helper never set them.\n\n    So every expectation created without NAT now exposed uninitialized\n    slab memory to userspace. This commit created the userspace read of\n    uninitialized fields; everything before it was dormant.\n\n 4. c7232c9979cba (v3.7): turns saved_ip into saved_addr and the check\n    into nf_inet_addr_cmp(..., \u0026any_addr). This is a refactor, and the\n    same bug carries through.\n\n 5. f09eca8db018 (v3.11-rc4), first partial fix. Its message states the\n    root problem directly: \"nf_ct_expect_alloc leaves unset the\n    expectation NAT fields. However, ctnetlink_exp_dump_expect expects\n    them to be zeroed\". It only patched nf_ct_expect_init(), and did not\n    zero dir.\n\n 6. 35177c687713 (v7.0-rc7), second partial fix, for the ctnetlink\n    path. It carries \"Fixes: 076a0ca02644\", which agrees with this root\n    cause.\n\n 7. 241ccd2fed90 (v7.2-rc2), the complete fix. Zeroing at allocation\n    closes the remaining broadcast-helper path and future omissions.\n\n4. Why not a different commit\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Not the broadcast or NetBIOS helper commits. When they were written,\n   nothing read the NAT fields for their expectations. They became a\n   leak only once 076a0ca02644 started exporting the fields.\n - Not c7232c9979cba. It is a rename and widening of the same check.\n - Not f09eca8db018 or 35177c687713. Each is a partial fix for paths\n   that 076a0ca02644 made leak.\n - A minor, separate side-issue that is not the root cause:\n   nf_ct_expect_init() never sets exp-\u003etuple.dst.dir. Since\n3c79107631db (v5.1), nf_expect_get_id() runs siphash() over the whole\nexp-\u003etuple, so that uninitialized byte feeds into CTA_EXPECT_ID. The\nmemory checker KMSAN would flag that as uninitialized data reaching\nuserspace. It is not a recoverable disclosure, because the output is a\nkeyed hash, but the zalloc fixes it too.\n\nConclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nRoot cause: 076a0ca02644657b13e4af363f487ced2942e9cb (\"netfilter:\nctnetlink: add NAT support for expectations\"), v3.4-rc1.\n\nSuggested tag, the same one the sibling fix 35177c687713 uses:\n\n    Fixes: 076a0ca02644 (\"netfilter: ctnetlink: add NAT support for expectations\")\n\nAffected range: v3.4 up to v7.2-rc1 for the broadcast-helper path. The\nhelper-init and ctnetlink-create paths were already closed in v3.11 and\nv7.0 respectively.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "7dae72e1e533407530b9f7f6e2b1597979de2786",
      "tree": "ea1b48ce37ad3cd0a0540e24cc33958a09a070f1",
      "parents": [
        "1c5318a203bf46ed6b2875ac659503d9768abbfb"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:51:01 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98872: Add .vulnerable file\n\nRoot cause: 1da177e4c3f41 (\"Linux-2.6.12-rc2\", 2005-04-16), the first\ngit commit.\n\nThe root cause is the \"default: BUG();\" arm of the type switch in\nvxfs_bmap_typed(). It was present in the first git commit, 1da177e4c3f41\n(\"Linux-2.6.12-rc2\", 2005-04-16), and that is the commit to blame. The\ncode is older than git: it comes from Christoph Hellwig\u0027s original\nfreevxfs driver (copyright 2000-2001), so 1da177e4c3f41 is the earliest\ncommit origin/master can point to. No later commit introduced or changed\nthe bad behaviour.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n704d48d81dc41 (\"freevxfs: don\u0027t BUG() on unknown typed-extent type\")\ntouches one line in fs/freevxfs/vxfs_bmap.c, the default: case of the\ntype switch in vxfs_bmap_typed():\n\n            default:\n    -               BUG();\n    +               WARN_ON_ONCE(1);\n    +               return 0;\n\nThe function now returns 0, which is already its \"no mapping\" value (the\nDEV4 case returns it too). vxfs_getblk() turns 0 into -EIO, so the\nrequest fails cleanly instead of oopsing.\n\n2. The underlying bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nA VxFS inode with vdi_orgtype \u003d\u003d VXFS_ORG_TYPED holds VXFS_NTYPED (6)\nstruct vxfs_typed descriptors in its on-disk org area:\n\n    struct vxfs_typed {\n            __fs64 vt_hdr;   /* Header, 0xTTOOOOOOOOOOOOOO; T\u003dtype, O\u003doffs */\n            __fs32 vt_block;\n            __fs32 vt_size;\n    };\n\n - The type comes straight from disk. Bits 63..56 of vt_hdr hold the\n   type and bits 55..0 the logical offset. The driver defines only four\n   types: VXFS_TYPED_INDIRECT\u003d1, VXFS_TYPED_DATA\u003d2,\n   VXFS_TYPED_INDIRECT_DEV4\u003d3 and VXFS_TYPED_DATA_DEV4\u003d4.\n - Nothing validates it. When an inode is loaded, the org area is\n   copied verbatim from the disk inode:\n    - in 1da177e4c3f41, memcpy(vip, dip, sizeof(*vip)) in __vxfs_iget()\n      / vxfs_blkiget();\n    - today, memcpy(\u0026vip-\u003evii_org, \u0026dip-\u003evdi_org, ...) in dip2vip_cpy().\n   No code checks the 8-bit type against the known values.\n - The switch treats a disk value as a kernel invariant.\nvxfs_bmap_typed() loops over the six slots. Any slot with off \u003c\u003d iblock\nreaches switch ((u32)(hdr \u003e\u003e VXFS_TYPED_TYPESHIFT)).\n   Types 1-4 are handled. Type 0 and types 5-255 hit \"default: BUG();\".\n\nThat is the design error: BUG() should only guard against kernel bugs,\nbut here it fires on a byte any filesystem image can set. A crafted or\nsimply corrupted image takes down the kernel through BUG() -\u003e\ninvalid_opcode -\u003e oops, or a full panic with panic_on_oops. The function\nalready had a clean failure value (0) and simply didn\u0027t use it here.\n\nType 0, which an all-zero slot would also produce, falls into the same\ndefault. Whether real VxFS images zero-fill unused slots has not been\nchecked, so treat that as a possibility rather than a confirmed second\ntrigger.\n\n3. How the crash is reached\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Someone with mount privilege mounts a crafted VxFS image.\n    vxfs_fs_type is FS_REQUIRES_DEV and not userns-mountable.\n 2. A regular file in that image has vdi_orgtype \u003d VXFS_ORG_TYPED. One\n    slot has off \u003c\u003d iblock and a type byte outside 1-4.\n 3. Two entry points lead to the same code:\n     - FIBMAP (the path in the report): ioctl(FIBMAP) -\u003e ioctl_fibmap()\n       (needs CAP_SYS_RAWIO) -\u003e bmap() -\u003e a_ops-\u003ebmap \u003d vxfs_bmap -\u003e\n       generic_block_bmap(mapping, block, vxfs_getblk) -\u003e vxfs_getblk()\n       -\u003e vxfs_bmap1().\n     - Ordinary reads: read() / page cache -\u003e .read_folio -\u003e\n       block_read_full_folio(folio, vxfs_getblk) -\u003e vxfs_getblk() -\u003e\n       vxfs_bmap1(), and vxfs_bread() -\u003e vxfs_bmap1(). These need no\n       special capability once the image is mounted.\n 4. vxfs_bmap1() sees VXFS_ISTYPED(vip) and calls vxfs_bmap_typed().\n 5. That reaches \"default: BUG();\", matching the report\u0027s \"kernel BUG at\n    fs/freevxfs/vxfs_bmap.c:230!\" in vxfs_bmap_typed [inline] via\n    vxfs_bmap1.\n\n4. History trace\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -L on vxfs_bmap_typed() up to the fix shows only three commits\ntouching the function:\n\n    Commit         Release          What it did to vxfs_bmap_typed()\n    -------------  ---------------  ---------------------------------------\n    1da177e4c3f41  v2.6.12-rc2      \"Linux-2.6.12-rc2\": function appears\n                   (2005)           with the switch on\n                                    typ-\u003evt_hdr \u003e\u003e VXFS_TYPED_TYPESHIFT,\n                                    cases 1-4, and \"default: BUG();\",\n                                    verbatim.\n                                    Root cause: yes, earliest commit in\n                                    git.\n    0d83f7fc83f77  v4.8-rc1 (2016)  \"freevxfs: handle big endian HP-UX\n                                    file systems\": added fs64_to_cpu()/\n                                    fs32_to_cpu() byte-swapping\n                                    (u64 hdr \u003d fs64_to_cpu(sbi,\n                                    typ-\u003evt_hdr)), u_int32_t -\u003e u32, new\n                                    printk formats. The \"default: BUG();\"\n                                    and the set of handled types are\n                                    unchanged.\n                                    Root cause: no, only nearby lines\n                                    changed.\n    704d48d81dc41  v7.2             BUG() -\u003e WARN_ON_ONCE(1); return 0;\n                                    The fix.\n\nThe other commits on the file (8cb681b9c7fff, 6e1e8e11c7885,\n82f703bb8cb27, 0b1e987c56640, ec3a8dd38199a, 0b2600f81cefc) don\u0027t touch\nthis function\u0027s body. The local ~/linux-history-tmp clone stops at\n1da177e4c3f41 too, so origin/master can\u0027t go any further back.\n\nThe paths into this code (.readpage/.bmap \u003d vxfs_bmap -\u003e\ngeneric_block_bmap(..., vxfs_getblk) -\u003e vxfs_bmap1()) are also already\nin vxfs_subr.c at 1da177e4c3f41. So the bug has been reachable from\nuserspace in every git-era kernel that ships freevxfs.\n\n5. Affected range and related notes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Affected: every kernel from 1da177e4c3f41 (2.6.12-rc2, and earlier\n   pre-git kernels) up to the fix in v7.2.\n - Stable trees: the fix has no Fixes: tag, but the correct one would\n   be \u0027Fixes: 1da177e4c3f4 (\"Linux-2.6.12-rc2\")\u0027. Every stable tree that\n   still builds fs/freevxfs is affected and the patch applies trivially.\n - Mainline: a3ed4fcd04b95 (\"freevxfs: remove the driver\"), by Christoph\nHellwig, v7.3-rc1, deletes the whole filesystem, so origin/master no\nlonger has this code at all.\n - Same pattern, not fixed by this commit:\n    - vxfs_bmap_indir() has the same \"default: BUG()\" on typed\n      descriptors read from on-disk indirect blocks. It is reached from\n      vxfs_bmap_typed() through a VXFS_TYPED_INDIRECT slot, and still\n      has the BUG() after 704d48d81dc41.\n    - vxfs_bmap1() calls BUG() on an unrecognised vii_orgtype.\nBoth also date to 1da177e4c3f41. In stable trees they survive this fix\nand are separate bugs from this CVE.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "1c5318a203bf46ed6b2875ac659503d9768abbfb",
      "tree": "461f02ab2da0408acc08cc1a4f0412a1e851b724",
      "parents": [
        "cf37150007b6dfc6a7534a406048980d64d8ab76"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:45:35 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98815: Add .vulnerable file\n\nRoot cause: 2637baed78010 (\"nvme: introduce generic per-namespace\nchardev\"), by Minwoo Im, first released in v5.13-rc1.\n\nThe bug is in every kernel that has the /dev/ngXnY generic char\ndevices. The fix, 9952f3882ecba (\"nvme: fix cdev lifetime\"), has no\nFixes: tag.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore the fix:\n\n - struct nvme_ns_head and struct nvme_ns each embed a struct cdev cdev\n   and a struct device cdev_device (drivers/nvme/host/nvme.h:556-557 and\n   :617-618).\n - The memory holding them is freed by kfree() when the object\u0027s own\n   refcount reaches zero: head-\u003eref in nvme_free_ns_head()\n   (core.c:683-694), ns-\u003ekref in nvme_free_ns() (core.c:711-719).\n - Open file descriptors were tied to that object only through the fops:\n    - nvme_ns_head_chr_open() calls nvme_tryget_ns_head(), and\n      nvme_ns_head_chr_release() calls nvme_put_ns_head()\n      (multipath.c:638-649).\n    - nvme_ns_chr_open()/nvme_ns_chr_release() go through\n      nvme_ns_open()/nvme_ns_release(), which take and drop ns-\u003ekref\n      (core.c:1800-1824, :3942-3951).\n\nThe fix removes the head .open/.release methods. Instead it takes\nnvme_get_ns_head(head) or nvme_get_ns(ns) before nvme_cdev_add() and\ndrops that reference in the cdev_device release callback,\nnvme_cdev_rel(). The containing object can then no longer be freed\nbefore the cdev\u0027s kobject is gone.\n\n2. Why tying the lifetime to open/release is wrong\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe char-dev core keeps its own refcount on an open struct cdev,\nseparate from the driver\u0027s:\n\n - On open, chrdev_open() calls cdev_get(p), which takes a kobject\n   reference on p-\u003ekobj and a module reference, and stores inode-\u003ei_cdev\n   \u003d p. Only then does it call f_op-\u003eopen.\n - When the cdev is added, cdev_device_add() calls\n   cdev_set_parent(cdev, \u0026dev-\u003ekobj), and cdev_add() then does\n   kobject_get(p-\u003ekobj.parent). So the cdev pins cdev_device until\n   cdev_default_release() runs kobject_put(parent). The kerneldoc on\n   cdev_device_add() says this is meant to keep the parent alive \"until\n   all references to the cdev are released\". That only helps if the\n   parent device\u0027s lifetime is what controls the memory.\n - On close, __fput() runs things in this order (fs/file_table.c,\n   unchanged since well before v5.13):\n\n       if (file-\u003ef_op-\u003erelease)\n               file-\u003ef_op-\u003erelease(inode, file);        /* (1) driver drops its ref */\n       if (S_ISCHR(inode-\u003ei_mode) \u0026\u0026 inode-\u003ei_cdev !\u003d NULL \u0026\u0026 !(mode \u0026 FMODE_PATH))\n               cdev_put(inode-\u003ei_cdev);                 /* (2) cdev core drops its ref */\n\ncdev_put() reads p-\u003eowner, then calls kobject_put(\u0026p-\u003ekobj) and\nmodule_put(owner).\n\nSo if a driver frees the structure that contains the cdev from inside\n-\u003erelease, step (2) runs on freed memory. Once the device has been torn\ndown, the open fd\u0027s reference is the last one, so the -\u003erelease put is\nthe final put and frees the head (or ns). This is exactly what the fix\u0027s\nKASAN report shows:\n\n - Freed by: __fput -\u003e nvme_ns_head_chr_release -\u003e nvme_free_ns_head\n   -\u003e kfree.\n - Bad access: __fput -\u003e cdev_put.part.0, \"Read of size 8\", which\n   matches reading the p-\u003eowner pointer.\n\n3. How 2637baed78010 introduced it\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThat commit created the whole design that the fix corrects:\n\n - Embedding. It added struct cdev cdev; struct device cdev_device;\ndirectly into struct nvme_ns_head and struct nvme_ns. Both are kfree()d\nby their own krefs, and nothing ties them to the cdev\u0027s kobject.\n - Lifetime through fops. It added:\n\n       static int nvme_ns_head_chr_open(...)  { if (!nvme_tryget_ns_head(cdev_to_ns_head(inode-\u003ei_cdev))) return -ENXIO; ... }\n       static int nvme_ns_head_chr_release(...) { nvme_put_ns_head(cdev_to_ns_head(inode-\u003ei_cdev)); ... }\n       static int nvme_ns_chr_open(...)    { return nvme_ns_open(container_of(inode-\u003ei_cdev, struct nvme_ns, cdev)); }\n       static int nvme_ns_chr_release(...) { nvme_ns_release(container_of(inode-\u003ei_cdev, struct nvme_ns, cdev)); ... }\n\n   These are the exact functions the fix removes or works around.\n - No owning release. nvme_cdev_add() set up cdev_device with no\n-\u003erelease and no reference on the owner. The parent pin taken by\ncdev_add() therefore protected nothing: head-\u003ecdev_device could be\npinned by the cdev while the memory around it had already been freed.\n\nAt that commit the head use-after-free was already reachable. There,\nnvme_cdev_del() was called from nvme_mpath_remove_disk(), which ran\ninside the kref release nvme_free_ns_head():\n\n 1. Open /dev/ngXnY. cdev_get() takes a reference on head-\u003ecdev.kobj,\n    and nvme_tryget_ns_head() increments head-\u003eref.\n 2. Remove the controllers or paths. All other head references are\n    dropped, so the fd holds the last one.\n 3. close(fd) -\u003e __fput -\u003e nvme_ns_head_chr_release -\u003e nvme_put_ns_head\n    -\u003e nvme_free_ns_head, which runs:\n     - nvme_mpath_remove_disk -\u003e nvme_cdev_del -\u003e cdev_device_del. This\n       drops the cdev\u0027s initial reference, but the fd\u0027s reference is\n       still there.\n     - kfree(head), which frees head-\u003ecdev and head-\u003ecdev_device.\n 4. __fput -\u003e cdev_put(\u0026head-\u003ecdev) reads the freed p-\u003eowner, then\n    calls kobject_put on a freed kobject. That runs cdev_default_release\n    -\u003e cdev_purge (list operations on freed memory) -\u003e\n    kobject_put(\u0026head-\u003ecdev_device.kobj) on freed memory.\n\nThe non-multipath nvme_ns cdev fails the same way. nvme_ns_remove()\ndeletes the cdev and drops its own ns reference, so the open fd holds\nthe last ns-\u003ekref. On close(), nvme_ns_release -\u003e nvme_put_ns -\u003e\nnvme_free_ns -\u003e kfree(ns), and then cdev_put(\u0026ns-\u003ecdev) touches freed\nmemory. This is the \"same problem exists for the NS cdev\" case that the\nfix also handles.\n\n4. Later commits only moved code; none caused or fixed the bug\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n    Commit         Release    What it changed / effect on this bug\n    -------------  ---------  ---------------------------------------------\n    3596a06583a16  v5.13-rc4  \"fix potential memory leaks in\n                              nvme_cdev_add\": put_device() on the\n                              add-failure path.\n                              Effect: none.\n    5396fdac56d87  v5.14-rc3  \"fix refcounting imbalance when all paths\n                              are down\": moved head nvme_cdev_del() out\n                              of the kref release into a shutdown path.\n                              Effect: gives the fix\u0027s reported sequence\n                              (cdev deleted at teardown, head freed later\n                              by the fd\u0027s release); the underlying\n                              lifetime mismatch is unchanged.\n    be5eb93354262  v5.15-rc6  \"fix per-namespace chardev deletion\": added\n                              nvme_cdev_rel() and put_device().\n                              Effect: the release only frees the IDA\n                              minor; it does not own the containing\n                              object (the fix adds that).\n    62188639ec160  v6.16      Delayed head removal: introduced\n                              nvme_remove_head().\n                              Effect: code movement only.\n    26acdaa357cde  v7.2-rc1   \"fix crash and memory leak during invalid\n                              cdev teardown\": added the *_CDEV_LIVE bits.\n                              Effect: unrelated failure-path issue.\n\nJust before the fix (9952f3882ecba^), the fops ref handling, the\nembedding and the kfree() paths are structurally the same as what\n2637baed78010 added.\n\n5. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n2637baed78010 embedded a refcounted struct cdev and struct device\ninside nvme_ns_head and nvme_ns, whose memory is freed by unrelated\nkrefs. It tied those objects to open files only through\n-\u003eopen/-\u003erelease, but VFS calls cdev_put() after -\u003erelease, so the last\nclose frees the head or ns and then accesses the cdev inside it. The fix\nties the lifetimes together by having the cdev\u0027s device hold the head or\nns reference until nvme_cdev_rel().\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "cf37150007b6dfc6a7534a406048980d64d8ab76",
      "tree": "98a1120f48911096cc3335032f19eca5210e96cc",
      "parents": [
        "e05aadf7316866ee60f4329dd1010e5792a34f37"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:40:42 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98784: Add .vulnerable file\n\nRoot cause: 2cd46ad22383a (\"drm/amdgpu: add graphic pipeline\nimplementation for si v8\"), by Ken Wang. It was authored 2016-01-19,\ncommitted 2016-08-31, and first released in v4.9-rc1.\n\nThat commit created drivers/gpu/drm/amd/amdgpu/gfx_v6_0.c. It loaded\nfour firmware images during software init and never released them during\nsoftware teardown. The leak has existed in that form since the file was\ncreated. No later commit introduced it, moved it, or briefly fixed it\nand then broke it again.\n\nSuggested tag:\n\n    Fixes: 2cd46ad22383 (\"drm/amdgpu: add graphic pipeline implementation for si v8\")\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, cc172e177795c (cherry-pick of 386e81346f95, Cc: stable, no\nFixes: tag, in v7.3-rc6), adds:\n\n    static void gfx_v6_0_free_microcode(struct amdgpu_device *adev)\n    {\n    \tamdgpu_ucode_release(\u0026adev-\u003egfx.pfp_fw);\n    \tamdgpu_ucode_release(\u0026adev-\u003egfx.me_fw);\n    \tamdgpu_ucode_release(\u0026adev-\u003egfx.ce_fw);\n    \tamdgpu_ucode_release(\u0026adev-\u003egfx.rlc_fw);\n    }\n\nIt calls this at the end of gfx_v6_0_sw_fini(). amdgpu_ucode_release()\nis just release_firmware(*fw); *fw \u003d NULL;. So the bug is that the four\nstruct firmware handles requested for the SI graphics block are never\nreleased when the block is torn down.\n\n2. How the leak happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n1. Acquire. In the amdgpu IP-block lifecycle, gfx_v6_0_sw_init() calls\n   gfx_v6_0_init_microcode() (line 3181 before the fix).\n    - That function requests \u003cchip\u003e_pfp.bin, _me.bin, _ce.bin and\n      _rlc.bin for Tahiti, Pitcairn, Verde, Oland or Hainan.\n    - Each request goes through request_firmware() (today via\n      amdgpu_ucode_request()). That allocates a struct firmware and\n      takes a reference on the firmware loader\u0027s buffer holding the\n      image.\n    - The handles are stored in adev-\u003egfx.pfp_fw, me_fw, ce_fw and\n      rlc_fw.\n    - They stay in use after init. The CP microcode loader\n      (gfx_v6_0_cp_gfx_load_microcode()) and the RLC loader read -\u003edata\n      on every hw_init and resume, so keeping them for the device\u0027s\n      lifetime is intended.\n\n2. Missing release. The only place gfx_v6_0.c released these handles\n   was the out: error path of gfx_v6_0_init_microcode(), which runs when\n   a later request fails. On the success path nothing released them.\n   gfx_v6_0_sw_fini() freed the GDS buffer objects (in the original\n   version), the rings, the compute and RLC objects, and nothing else.\n\n3. No common fallback. Shared amdgpu code does not release these\n   handles instead. Before the fix, every release_firmware /\n   amdgpu_ucode_release on adev-\u003egfx.{pfp,me,ce,rlc}_fw is inside a\n   per-generation gfx_v*_0.c file. The four in gfx_v6_0.c are all in the\n   init error path.\n\n4. Pointers lost. After sw_fini, amdgpu_device_fini_sw() and the DRM\n   device release free the amdgpu_device itself, and with it the only\n   pointers to the four handles.\n    - Each probe/remove cycle on an SI GPU leaks four struct firmware\n      objects, and the images stay pinned because their reference is\n      never dropped.\n    - Triggers include driver unbind/rebind, rmmod amdgpu, hot-unplug,\n      VFIO/passthrough rebinds, and an init failure after sw_init\n      succeeded (the fini path unwinds through sw_fini).\n    - kmemleak reports it.\n    - Impact is a slow, repeatable kernel memory leak. It is not memory\n      corruption.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBlame at the fix\u0027s parent\n-------------------------\n\n    2cd46ad22383ab  325) static int gfx_v6_0_init_microcode(struct amdgpu_device *adev)\n    2cd46ad22383ab 3181) \tr \u003d gfx_v6_0_init_microcode(adev);\n    36aa9ab9c0c4a4 3241) static int gfx_v6_0_sw_fini(struct amdgpu_ip_block *ip_block)\n    2cd46ad22383ab 3242-3250, 3252-3254  (body, ring fini loops, return)\n    88dfc9a3dd4702 3251) \tamdgpu_gfx_rlc_fini(adev);\n\n - 36aa9ab9c0c4a only changed the callback signature to take struct\n   amdgpu_ip_block *.\n - 88dfc9a3dd470 only replaced gfx_v6_0_rlc_fini() with the shared\n   amdgpu_gfx_rlc_fini().\n\nNeither changed how firmware is owned.\n\nPickaxe over the whole file history (147 commits on origin/master)\n------------------------------------------------------------------\n\n - git log -S release_firmware -- gfx_v6_0.c finds only 2cd46ad22383a\n   (which added the error-path calls) and 39d3649b1647d.\n - git log -S amdgpu_ucode_release -- gfx_v6_0.c finds only\n   39d3649b1647d and the fix.\n - git log -S free_microcode -- gfx_v6_0.c finds only the fix.\n\n39d3649b1647d (\"drm/amd: Use amdgpu_ucode_* helpers for GFX6\", 2023)\nonly converted the init error path from release_firmware() + \u003d NULL to\namdgpu_ucode_release(). It never touched teardown. So no teardown\nrelease ever existed in gfx_v6_0.c to be removed later. The missing\nrelease dates from the file\u0027s creation.\n\nThe original file at 2cd46ad22383a\n----------------------------------\n\n    static int gfx_v6_0_sw_fini(void *handle)\n    {\n    \t...\n    \tamdgpu_bo_unref(\u0026adev-\u003egds.oa_gfx_bo);\n    \tamdgpu_bo_unref(\u0026adev-\u003egds.gws_gfx_bo);\n    \tamdgpu_bo_unref(\u0026adev-\u003egds.gds_gfx_bo);\n    \tfor (i \u003d 0; i \u003c adev-\u003egfx.num_gfx_rings; i++)\n    \t\tamdgpu_ring_fini(\u0026adev-\u003egfx.gfx_ring[i]);\n    \tfor (i \u003d 0; i \u003c adev-\u003egfx.num_compute_rings; i++)\n    \t\tamdgpu_ring_fini(\u0026adev-\u003egfx.compute_ring[i]);\n    \tgfx_v6_0_cp_compute_fini(adev);\n    \tgfx_v6_0_rlc_fini(adev);\n    \treturn 0;            /* pfp/me/ce/rlc firmware never released */\n    }\n\ngfx_v6_0_sw_init() in the same commit calls gfx_v6_0_init_microcode(),\nwhich does four request_firmware() calls (then from\nradeon/\u003cchip\u003e_*.bin). The only release_firmware() calls sit under out:\nif (err). The request without a matching teardown release has been there\nsince this commit.\n\n4. Why GFX6 was missed\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe same bug existed in the other generations and was fixed there:\n\n    Commit         Change                                      Release\n    -------------  ------------------------------------------  ----------\n    13331ac384e82  \"drm/amdgpu: fix gfx8 ucode mem leak\"       v4.7-rc3\n                   (Monk Liu, 2016-05-30)\n    e517cd77ee76a  \"drm/amdgpu: fix gfx 7 ucode mem leak\"      v4.7-rc3\n                   (Monk Liu, 2016-05-30)\n    c833d8aa4d829  \"drm/amdgpu:fix firmware memoryleak(v2)\",   v4.15-rc1\n                   GFX9 + PSP + GMC\n\nThe GFX7 fix added gfx_v7_0_free_microcode() to gfx_v7_0_sw_fini().\nThat is the same shape as the new GFX6 fix, line for line.\n\nThe SI support series was being developed and rebased outside mainline\nwhen those fixes landed in June 2016. Its changelog lists \"v5/v6/v7:\nrebase fixes\". It was merged in August 2016 for v4.9 without the\nfree_microcode teardown its GFX7/GFX8 siblings had just received. GFX6\nthen carried the leak for about ten years until cc172e177795c.\n\n5. Affected range\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Every kernel from v4.9-rc1 (when gfx_v6_0.c arrived with\n2cd46ad22383a) up to the fix in v7.3-rc6. That covers all active stable\ntrees.\n - Only SI GPUs (Tahiti, Pitcairn, Verde, Oland, Hainan) driven by\n   amdgpu are affected. That needs CONFIG_DRM_AMDGPU_SI and amdgpu\n   taking the device instead of radeon, for example amdgpu.si_support\u003d1\n   radeon.si_support\u003d0.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e05aadf7316866ee60f4329dd1010e5792a34f37",
      "tree": "95555b14d094302345bb58688b7685991ec72f43",
      "parents": [
        "e6773ee8339608706fb9cf4b89b8c5acc27d97fa"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:36:00 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98755: Add .vulnerable file\n\nRoot cause: 8868e44a5616 (\"sparc64: vcc: Create sysfs attribute\ngroup\"), by Jag Raman, first released in v4.14-rc1 (August 2017). That\ncommit created vcc_send_ctl() with the uninitialized stack packet, and\nnothing changed the function until the fix.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, 1f71f565e358 (Joshua Rogers, merged for v7.3-rc6, Cc: stable,\nno Fixes: tag), changes one line in drivers/tty/vcc.c:\n\n     static int vcc_send_ctl(struct vcc_port *port, int ctl)\n     {\n    -\tstruct vio_vcc pkt;\n    +\tstruct vio_vcc pkt \u003d {};\n     \tint rv;\n\n     \tpkt.tag.type \u003d VIO_TYPE_CTRL;\n     \tpkt.tag.sid \u003d ctl;\n     \tpkt.tag.stype \u003d 0;\n\n     \trv \u003d ldc_write(port-\u003evio.lp, \u0026pkt, sizeof(pkt.tag));\n\n2. Why this leaks kernel stack data\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe wire header\n---------------\n\nstruct vio_vcc (arch/sparc/include/asm/vio.h:290) is a struct\nvio_msg_tag header followed by char data[VIO_VCC_MTU_SIZE]. The header\n(vio.h:17) is:\n\n    offset  field           size  set by vcc_send_ctl()?\n    ------  --------------  ----  -----------------------------------\n    0       u8  type        1     yes, VIO_TYPE_CTRL\n    1       u8  stype       1     yes, 0\n    2       u16 stype_env   2     NO\n    4       u32 sid         4     yes, ctl (VCC_CTL_BREAK \u003d -1 or\n                                  VCC_CTL_HUP \u003d -2)\n\nsizeof(struct vio_msg_tag) is 8 and the struct has no padding. So the\nonly uninitialized bytes are the 2 bytes of stype_env, and they hold\nwhatever the previous call frame left on the stack.\n\nThe transmit length\n-------------------\n\nldc_write() is called with sizeof(pkt.tag), so exactly those 8 bytes go\nout. The 1016-byte data[] area is also uninitialized but never\ntransmitted, so the exposure is limited to stype_env.\n\nThe transport\n-------------\n\nThe vcc LDC channel is configured with .mode \u003d LDC_MODE_RAW\n(drivers/tty/vcc.c:466). The path is:\n\n 1. ldc_write() (arch/sparc/kernel/ldc.c:1937) calls lp-\u003emops-\u003ewrite,\n    which is write_raw().\n 2. write_raw() calls data_get_tx_packet() to get a slot in the\n    hypervisor-registered TX queue.\n 3. It then does memcpy(p, buf, size) with no header processing and no\n    sanitization.\n 4. send_tx_packet() advances the queue tail through\n    sun4v_ldc_tx_set_qtail(), and the hypervisor delivers the packet to\n    the other end of the channel.\n\nWho receives it\n---------------\n\nEach vcc port is the console concentrator endpoint for a logical\ndomain. vcc_probe() reads the port\u0027s vcc-domain-name from the machine\ndescription (vcc.c:624), so the peer is a separate guest domain. The two\nbytes therefore cross a domain boundary, from the kernel serving the\nconsoles to the domain on the other end. stype_env is a real wire field:\nvcc\u0027s own receive path prints pkt.tag.stype_env (vcc.c:336,\n:345, :851), so the peer reads it as protocol data.\n\nHow it is triggered\n-------------------\n\nThree paths send this packet:\n\n - Writing the sysfs \"break\" attribute calls break_store(), which calls\n   vcc_send_ctl(port, VCC_CTL_BREAK). This path was added in the same\n   commit as the bug.\n - The break_ctl tty operation, vcc_break_ctl(), sends VCC_CTL_BREAK.\n   Added by e942e5775c87.\n - The hangup tty operation, vcc_hangup(), sends VCC_CTL_HUP. Added by\n   6a3ff25bc6cd.\n\nEach call sends 2 fresh bytes of stack content to the peer.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe function\u0027s full history\n---------------------------\n\ngit log -L \u0027/static int vcc_send_ctl/,/^}/:drivers/tty/vcc.c\u0027\norigin/master returns only two commits:\n\n    1f71f565e3585 tty: vcc: zero-initialize control packet in vcc_send_ctl()   \u003c- fix\n    8868e44a56160 sparc64: vcc: Create sysfs attribute group                  \u003c- origin\n\nSo the function was never moved, renamed or refactored between creation\nand fix. Commit 8868e44a5616 added it exactly as it stood before the\nfix:\n\n    +static int vcc_send_ctl(struct vcc_port *port, int ctl)\n    +{\n    +\tstruct vio_vcc pkt;\n    +\tint rv;\n    +\n    +\tpkt.tag.type \u003d VIO_TYPE_CTRL;\n    +\tpkt.tag.sid \u003d ctl;\n    +\tpkt.tag.stype \u003d 0;\n    +\n    +\trv \u003d ldc_write(port-\u003evio.lp, \u0026pkt, sizeof(pkt.tag));\n    +\tWARN_ON(!rv);\n    +\tvccdbg(\"VCC: ldc_write(%ld)\u003d%d\\n\", sizeof(pkt.tag), rv);\n    +\n    +\treturn rv;\n    +}\n\nIt also added the first caller, the write-only (0200) sysfs \"break\"\nattribute, through vcc_sysfs_break_store(). From that commit on, the\nkernel sent 2 uninitialized stack bytes to the LDC peer.\n\nCommits that are not the root cause\n-----------------------------------\n\n - The vio_msg_tag header. It has had the u16 stype_env field since the\n   original sparc64 LDOM VIO code; a439fe51a1f8 only moved the header\n   into arch/sparc/include. The field is a legitimate part of the VIO\n   protocol. Every other VIO sender (vnet, vdc, vio_send_sid() users)\n   sets it, and the layout itself is fine.\n - 5d171050e28f (\"sparc64: vcc: Enable VCC port probe and removal\"). It\n   added struct vio_vcc to vio.h, but defining a struct does not leak\n   anything. The leak needs a sender that fills it partially.\n - 55bd2133fc3c5, f283ebd5642d8, ce808b7463259. These earlier commits\n   in the series do not contain vcc_send_ctl() or any control-packet\n   transmit.\n - 6a3ff25bc6cd (hangup) and e942e5775c87 (break_ctl). Both landed\n   later in the same v4.14 series. They call the already-buggy function\n   from tty operations, which makes it reachable from ordinary tty\n   actions such as hangup and tcsendbreak()/TIOCSBRK, not only sysfs.\n   They widen exposure but did not create the bug.\n\n4. Summary\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe root cause is in 8868e44a5616. It created vcc_send_ctl(), which\nbuilds the 8-byte VIO control header in a stack variable, sets only\ntype, stype and sid, and passes all 8 bytes to ldc_write(). In raw LDC\nmode those bytes are copied unchanged into the hypervisor transmit queue\nand delivered to the other domain, so 2 bytes of kernel stack\n(tag.stype_env) leak on every break or hangup control message.\n\n - Vulnerable range: v4.14-rc1 through v7.3-rc5.\n - Missing tag: the fix carries no Fixes: line. The appropriate one\n   would be:\n\n    Fixes: 8868e44a5616 (\"sparc64: vcc: Create sysfs attribute group\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "e6773ee8339608706fb9cf4b89b8c5acc27d97fa",
      "tree": "13cba0a8830b0e30cfe788b079cb584f4bbd3a23",
      "parents": [
        "b916a75395d9e69647000813a631ae4fd7dfa9c2"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:31:28 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98754: Add .vulnerable file\n\nRoot cause: 8f03f948ba356b7cb254b45a4e78b5b3d86fd3bb (\"sparc64: vcc:\nAdd install \u0026 cleanup TTY operations\"), by Jag Raman, dated 2017-08-15,\nfirst released in v4.14-rc1. Fixed in v7.3-rc6 by 03b49c991abb (\"tty:\nvcc: hold port lock when clearing tty pointer in vcc_cleanup\").\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n     \tport \u003d vcc_get(tty-\u003eindex, true);\n     \tif (port) {\n    -\t\tport-\u003etty \u003d NULL;\n    +\t\tscoped_guard(spinlock_irqsave, \u0026port-\u003elock)\n    +\t\t\tport-\u003etty \u003d NULL;\n\nvcc_cleanup() cleared port-\u003etty without taking port-\u003elock. It then\nfreed the tty_port that port-\u003etty-\u003eport points to\n(drivers/tty/vcc.c:1002-1004). Meanwhile, the LDC event callback reads\nport-\u003etty under port-\u003elock and dereferences tty-\u003eport.\n\n2. The race\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nReader: the LDC event path (drivers/tty/vcc.c:432-461, 306-355)\n\n - The sparc LDC core calls vcc_event() from its RX interrupt handler:\n   ldc_rx() -\u003e send_events() -\u003e lp-\u003ecfg.event(lp-\u003eevent_arg, ...)\n   (arch/sparc/kernel/ldc.c:779-789).\n - vcc_event() gets port straight from arg and only takes port-\u003elock.\n   It never calls vcc_get(), so it holds no reference on the port.\n - On LDC_EVENT_DATA_READY it calls vcc_ldc_read(). That loads tty \u003d\n   port-\u003etty, and if it is non-NULL it loops:\n     - vcc_rx_check() -\u003e tty_buffer_request_room(tty-\u003eport, ...)\n     - vcc_rx() -\u003e tty_insert_flip_string(tty-\u003eport, ...) and\n       tty_flip_buffer_push(tty-\u003eport)\n\nWriter: the tty teardown path (drivers/tty/vcc.c:983-1005)\n\n - When the last reference to the tty_struct is dropped,\n   queue_release_one_tty() runs release_one_tty(), which calls\n   tty-\u003eops-\u003ecleanup (drivers/tty/tty_io.c:1516-1524).\n - vcc_cleanup() takes the exclusive vcc_get(tty-\u003eindex, true), writes\n   port-\u003etty \u003d NULL without port-\u003elock, and then calls\n   tty_port_destroy(tty-\u003eport) and kfree(tty-\u003eport).\n\nHow the use-after-free happens:\n\n  CPU0 (LDC RX IRQ)                    CPU1 (release_one_tty work)\n----------------- --------------------------- vcc_event(): takes\nport-\u003elock vcc_ldc_read(): tty \u003d port-\u003etty\n    (still valid)\n                                       vcc_cleanup(): vcc_get(excl)\n                                         succeeds, because vcc_event\n                                         holds no reference\n                                       port-\u003etty \u003d NULL (no lock, so it\n                                         is not serialized with CPU0)\n                                       tty_port_destroy(tty-\u003eport);\n                                       kfree(tty-\u003eport) vcc_rx_check() / vcc_rx() use\n    tty-\u003eport -\u003e UAF on the freed\n    tty_port and its flip buffers\n\nThe driver protects vcc_port state in two ways. One is the\nvcc_get()/vcc_put() refcount plus the exclusive flag. The other is the\nport-\u003elock spinlock. vcc_event() only uses the spinlock, and\nvcc_cleanup() only used the refcount, so nothing ordered the two against\neach other.\n\nWith the fix, vcc_cleanup() takes port-\u003elock before clearing the\npointer, so it waits for any vcc_event() already running in\nvcc_ldc_read(). Any later event sees port-\u003etty \u003d\u003d NULL and only calls\nldc_rx_reset(). The lock is released before tty_port_destroy()/kfree(),\nso no reader can still hold the old pointer when the tty_port is freed.\n\n3. Why 8f03f948ba35 is the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nEvery commit in the original 2017 vcc series on origin/master was\nwalked through:\n\n - 5d171050e28f (probe/removal): adds an empty vcc_event() stub and\n   port-\u003elock. port-\u003etty is only read, in vcc_remove.\n - f8c553354e71 (RX/TX timers): adds vcc_ldc_read(), which reads\n   port-\u003etty and uses tty-\u003eport.\n - 103b9b0bc9e0 (LDC event engine): fills in vcc_event() so it calls\n   vcc_ldc_read() under port-\u003elock with no reference. This is the reader\n   side. However, at this commit nothing assigns port-\u003etty:\n   grepping the file at e942e5775c87, the commit just before\n8f03f948ba35, finds no \"-\u003etty \u003d\" writes. The pointer is always NULL, so\nvcc_event always takes the ldc_rx_reset() branch and there is nothing to\nrace with.\n - 8f03f948ba35 (install \u0026 cleanup) adds both halves of the problem:\n     - vcc_install() sets port_vcc-\u003etty \u003d tty and tty-\u003eport \u003d port_tty.\n       From here, vcc_event can reach a live tty-\u003eport.\n     - vcc_cleanup() sets port-\u003etty \u003d NULL using only the exclusive\n       refcount, never port-\u003elock, then calls tty_port_destroy() and\n       kfree(tty-\u003eport).\n\nThat unlocked clear followed by the free is exactly the line the fix\nchanges. The vcc_cleanup() body is still essentially as it was written\nthen; later commits only removed the !tty check\n(5c58097eeb9a) and changed allocation helpers. There was no code\nmovement that carried the line in from somewhere else.\n\nSo the reader existed one commit earlier but was harmless. The race and\nuse-after-free became possible when 8f03f948ba35 started publishing a\nreal tty in port-\u003etty and clearing and freeing it without the lock. All\nof these commits landed together in v4.14-rc1, so every kernel since\nv4.14 with CONFIG_VCC (sparc64 sun4v LDoms control domain) is affected.\n\n4. A later commit that widened the race\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n4790b6dc7ad6 (\"tty: vcc: Convert timers to use timer_setup()\",\nv4.15-rc1) removed the vcc_get_ne() calls from vcc_rx_timer() and\nvcc_tx_timer(). Before that, the timers held a reference, so the\nexclusive vcc_get() in vcc_cleanup() kept them out. Afterwards they also\nrely only on port-\u003elock while reading port-\u003etty. This exposed the timer\npaths to the same unlocked clear, and the same fix closes them.\nIt is not the root cause for the vcc_event() race this CVE describes,\nwhich has existed since 8f03f948ba35.\n\nFixes tag:\n\n    Fixes: 8f03f948ba35 (\"sparc64: vcc: Add install \u0026 cleanup TTY operations\")\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "b916a75395d9e69647000813a631ae4fd7dfa9c2",
      "tree": "cb2e36668d62ccbf6319d46d70ac0761a3da94fc",
      "parents": [
        "f805f048084d63caa4c84e72d92ae1cb9d7c1525"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:27:32 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98714: Add .vulnerable file\n\nRoot cause: 8d28fec406e4d (\"KVM: SVM: Add clean-bits infrastructure\ncode\"), by Joerg Roedel, dated December 2010, first released in\nv2.6.38-rc1 (v2.6.38-rc1~244^2~45).\n\nThat commit added a call that marks every VMCB clean bit as \"clean\" at\nthe end of svm_vcpu_run(). It does this after every VMRUN, including\nVMRUNs the CPU rejected. That one statement is the bug the fix guards,\nand it has been in every kernel since v2.6.38.\n\nThe ERAP half of the fix is the same mistake repeated much later by\ndb5e82496492b (\"KVM: SVM: Virtualize and advertise support for ERAPS\",\nv7.0-rc1). It only affects v7.0 and newer, so it is covered by the range\nthat 8d28fec406e4d already opens.\n\n1. What the fix changes\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, e27fc915c8f57 (\"KVM: SVM: Update control fields on #VMEXIT if\nand only if VMRUN succeeded\", Sean Christopherson, v7.3-rc6, backported\nto 7.2.y as 8676b29d186fc in v7.2.10), moves two post-VMRUN updates\nunder the same !svm_is_vmrun_failure() check that its sibling patch\n60d93c27859fb added for tlb_ctl:\n\n    if (!svm_is_vmrun_failure(svm-\u003evmcb-\u003econtrol.exit_code)) {\n            this_cpu_ptr(\u0026svm_data)-\u003eflush_all_asids \u003d false;\n            svm-\u003evmcb-\u003econtrol.tlb_ctl \u003d TLB_CONTROL_DO_NOTHING;\n            if (cpu_feature_enabled(X86_FEATURE_ERAPS))\n                    svm-\u003evmcb-\u003econtrol.erap_ctl \u0026\u003d ~ERAP_CONTROL_CLEAR_RAP;   // moved in\n            vmcb_mark_all_clean(svm-\u003evmcb);                                    // moved in\n    }\n\nThe reason is AMD\u0027s statement quoted in the commit: there is no\narchitectural guarantee about what happens when VMRUN fails. So KVM must\nassume that the work requested by the control fields was not done.\n\nThe patch is 2/4 of the series\n\u003c20260904170642.3291466-1-seanjc@google.com\u003e. The siblings are:\n\n - 60d93c27859fb (\"KVM: SVM: Preserve TLB control (i.e. pending TLB\n   flush) on failed VMRUN\", tlb_ctl), which has its own\n   Fixes: 38e5e92fe8c0 tag.\n - 64c5b15af71ba (\"KVM: SVM: Don\u0027t mark ASID fields as dirty when\n   setting control.tlb_ctl\").\n - 73392706d0e0f (\"KVM: SVM: Sync guest\u0027s PERF_CNTR_GLOBAL_CTL from h/w\n   only on successful VMRUN\").\n\ne27fc915c8f57 has Cc: stable but no Fixes: tag, because its two fields\ncome from two different commits.\n\n2. How the bug works\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nVMCB clean bits. control.clean is a bitmap of groups:\n\n - VMCB_INTERCEPTS: intercept vectors, TSC offset\n - VMCB_PERM_MAP: IOPM and MSRPM base addresses\n - VMCB_ASID\n - VMCB_INTR\n - VMCB_NPT: nested paging enable, nested CR3, guest PAT\n - VMCB_CR: CR0, CR3, CR4, EFER\n - VMCB_DR, VMCB_DT, VMCB_SEG, VMCB_CR2, VMCB_LBR, VMCB_AVIC\n\nWhen a bit is 1, the CPU may use its own cached copy of that group from\nthe last time this VMCB ran on this CPU, instead of re-reading memory.\nKVM\u0027s contract is:\n\n - vmcb_mark_dirty() whenever it changes a field;\n - vmcb_mark_all_clean() only once the CPU has actually loaded the\n   VMCB.\n\nThe failure sequence:\n\n 1. VMCB X runs successfully on pCPU A, and the CPU caches its state.\n 2. KVM changes a field, for example the intercepts, nested CR3 or CR0,\n    and marks that group dirty.\n 3. VMRUN fails a consistency check and exits with VMEXIT_INVALID\n    (SVM_EXIT_ERR). The guest never ran, so the CPU may never have\n    reloaded the dirty group.\n 4. svm_vcpu_run() still calls vmcb_mark_all_clean(), so the dirty group\n    is now marked clean.\n 5. The failure is handled afterwards:\n     - For vmcb01, svm_handle_exit() returns KVM_EXIT_FAIL_ENTRY to\n       userspace. Userspace may fix the offending state, for example\n       with KVM_SET_SREGS, and run again.\n     - For vmcb02, nested_svm_intercept() returns NESTED_EXIT_DONE, so\n       the failure is reflected to L1. That makes the failed-VMRUN path\n       reachable from a guest, and L1 can VMRUN again.\n 6. The next VMRUN of the same VMCB on the same pCPU sees the group\n    marked clean. pre_svm_run() only marks everything dirty when the\n    pCPU changes. So the CPU may use the stale cached copy: old\n    intercepts, old IOPM/MSRPM bases, old nested CR3, old ASID, old\n    CR/EFER, old segments and so on. KVM believes the guest is running\n    with state it never got.\n\nERAP part. Before VMRUN, KVM sets CLEAR_RAP when a\nreturn-address-predictor flush is owed: after a nested #VMEXIT into\nvmcb01, on emulated guest TLB flushes, or on CR3 writes. After VMRUN it\ncleared the bit unconditionally. If VMRUN failed, the RAP was never\nflushed and the request was lost. The next successful entry would then\nrun without the flush, which removes the L1/L2 RSB-poisoning protection\nERAPS is meant to provide.\n\n3. Tracing the line back to its origin\n   \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ngit log -G\u0027mark_all_clean\\(svm-\u003evmcb\\)\u0027 finds only four commits that\ntouch the line, plus a file move:\n\n - 8d28fec406e4d \"KVM: SVM: Add clean-bits infrastructure code\"\n(v2.6.38-rc1): adds mark_all_clean(svm-\u003evmcb); as the last statement of\nsvm_vcpu_run(), with no check of exit_code.\n - 116a0a23676e7 \"KVM: SVM: Add clean-bit for intercepts, tsc-offset\n   and pause filter count\" (v2.6.38-rc1): defines the first real bit,\n   VMCB_INTERCEPTS. This makes the call have an effect (see note below).\n - 46a010dd68960 (v5.7-rc1): moves svm.c to svm/svm.c.\n - 06e7852c0ffb3 \"KVM: SVM: Add vmcb_ prefix to mark_*() functions\"\n   (v5.9-rc1): renames it to vmcb_mark_all_clean().\n - e42c68281b444 \"KVM: SVM: avoid emulation with stale next_rip\"\n(v5.9-rc5): moves the call up, next to the tlb_ctl reset, still\nunconditional.\n - db5e82496492b ERAPS support (v7.0-rc1): adds erap_ctl \u0026\u003d ~CLEAR_RAP\n   right before it, copying the same unconditional pattern.\n - e27fc915c8f57 (the fix): first commit to make it depend on VMRUN\n   succeeding.\n\nNo commit in between ever guarded the call. It is unconditional in\nv5.4, v5.10, v6.6 and v6.12; in v6.12 it reads:\n\n    svm-\u003evmcb-\u003econtrol.tlb_ctl \u003d TLB_CONTROL_DO_NOTHING;\n    vmcb_mark_all_clean(svm-\u003evmcb);\n\nFailed VMRUNs went through this path at the time. In the 8d28fec406e4d\ntree, svm_vcpu_run() runs VMLOAD/VMRUN/VMSAVE and then falls straight\nthrough to mark_all_clean(), with no early return on failure:\n\n    @@ -3490,6 +3519,8 @@ static void svm_vcpu_run(struct kvm_vcpu *vcpu)\n            if (unlikely(svm-\u003evmcb-\u003econtrol.exit_code \u003d\u003d\n                         SVM_EXIT_EXCP_BASE + MC_VECTOR))\n                    svm_handle_mce(svm);\n    +\n    +       mark_all_clean(svm-\u003evmcb);\n     }\n\nThe SVM_EXIT_ERR check that reports KVM_EXIT_FAIL_ENTRY lives in\nhandle_exit(), which runs later. So from the first day clean bits\nexisted, every rejected VMRUN left the VMCB marked fully clean.\n\nNote on 116a0a23676e7. In 8d28fec406e4d itself VMCB_DIRTY_MAX was 0:\n\n    enum {\n            VMCB_DIRTY_MAX,\n    };\n\nso mark_all_clean() wrote ((1 \u003c\u003c 0) - 1) \u003d\u003d 0 and had no effect. It\nstarted changing behaviour one commit later, when 116a0a23676e7\n(v2.6.38-rc1~244^2~44) defined the first bit. The faulty logic, marking\nclean regardless of VMRUN\u0027s outcome, was written in 8d28fec406e4d. Both\ncommits are in the same v2.6.38-rc1 merge window, so the affected range\nis the same either way.\n\nThe ERAP half has no older origin. erap_ctl and ERAP_CONTROL_CLEAR_RAP\nfirst appear in db5e82496492b; git log -S finds no earlier commit.\n\nThe lore thread for the series was not available, so the trace above\nrelies only on the commits in the tree.\n\n4. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe root cause is 8d28fec406e4d. Its unconditional\nmark_all_clean(svm-\u003evmcb) at the end of svm_vcpu_run() has survived\nevery rename and move unchanged until e27fc915c8f57. Suggested tag for\nthe clean-bit half:\n\n    Fixes: 8d28fec406e4 (\"KVM: SVM: Add clean-bits infrastructure code\")\n\nFor the ERAP half, which only matters on v7.0+:\n\n    Fixes: db5e82496492 (\"KVM: SVM: Virtualize and advertise support for ERAPS\")\n\nThis is a single-fix CVE, so .vulnerable records the one boundary,\n8d28fec406e4d, which already covers the v7.0+ range of the ERAP half.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "f805f048084d63caa4c84e72d92ae1cb9d7c1525",
      "tree": "b824f027e79bda1534a18c3759ae82834fbac87e",
      "parents": [
        "06c0386be9900758429a2931866d2663899b9b98"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 08:19:04 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98439: Add .vulnerable file\n\nRoot cause: 0626e6641f6b4 (\"cifsd: add server handler for central\nprocessing and tranport layers\"), by Namjae Jeon, first released in\nv5.15-rc1 (v5.15-rc1~11^2~5^2~94).\n\nIts connection teardown waits for every in-flight request to finish,\nand nothing ever cancels the requests that can wait forever.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, 4fd5bad647bfa (\"smb/server: cancel async requests when closing\nconnection\", ChenXiaoSong, Suggested-by Namjae Jeon, v7.3-rc1, no\nFixes: or Cc: stable tag), adds one call in ksmbd_conn_handler_loop():\n\n        ksmbd_conn_set_releasing(conn);\n    +   ksmbd_conn_cancel_async_requests(conn);\n        /* Wait till all reference dropped to the Server object*/\n        wait_event(conn-\u003er_count_q, atomic_read(\u0026conn-\u003er_count) \u003d\u003d 0);\n        ...\n        default_conn_ops.terminate_fn(conn);\n\nThe new helper walks conn-\u003easync_requests under conn-\u003erequest_lock. For\nevery work item still in KSMBD_WORK_ACTIVE it sets KSMBD_WORK_CANCELLED\nand calls work-\u003ecancel_fn(work-\u003ecancel_argv). This is the same thing\nsmb2_cancel() does for a single client-sent SMB2_CANCEL, applied to all\npending async work when the connection dies.\n\n2. The bug: a circular wait at connection teardown\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nEvery request holds a count on conn-\u003er_count while it runs:\n\n - queue_ksmbd_work() calls ksmbd_conn_r_count_inc(conn) before\n   queueing the work.\n - handle_ksmbd_work() calls ksmbd_conn_r_count_dec(conn) only after\n   the handler returns.\n\nWhen the receive loop exits (socket closed, bad PDU, and so on), the\nhandler thread:\n\n 1. waits until r_count \u003d\u003d 0;\n 2. only then calls terminate_fn, which is\n    ksmbd_server_terminate_conn(). That runs\n    ksmbd_sessions_deregister(), then ksmbd_session_destroy(), then\n    ksmbd_destroy_file_table().\n\nA handler that went async and is sleeping in a worker keeps r_count\nabove zero. Only three things can wake it:\n\n - an SMB2_CANCEL from the client, which can\u0027t arrive because the\n   connection is gone;\n - an SMB2_CLOSE or the file-table teardown\n(set_close_state_blocked_works()), which only runs inside terminate_fn,\nafter the wait;\n - the other side releasing its resource.\n\nSo the handler thread waits for a worker that can only be woken by code\nthe handler thread runs after the wait.\n\nThe concrete trigger is a blocking SMB2_LOCK (fs/smb/server/smb2pdu.c\nat the fix\u0027s parent):\n\n - A lock request without SMB2_LOCKFLAG_FAIL_IMMEDIATELY skips the\n   conflict scan (goto no_check_cl).\n - It uses F_SETLKW with FL_SLEEP, so vfs_lock_file() returns\n   FILE_LOCK_DEFERRED.\n - The handler calls setup_async_work(work, smb2_remove_blocked_lock,\n   argv), which puts the work on conn-\u003easync_requests with a cancel_fn.\n - It adds the work to fp-\u003eblocked_works, sends STATUS_PENDING, and\n   sleeps in ksmbd_vfs_posix_lock_wait(), an uninterruptible\n   wait_event(flock-\u003ec.flc_wait, !flock-\u003ec.flc_blocker), while still\n   holding r_count.\n\nReproduction scenario:\n\n 1. One client opens the same file twice. ksmbd sets fl_owner \u003d filp,\n    so the two handles conflict even on one connection.\n 2. It takes an exclusive lock on handle A.\n 3. It sends a blocking overlapping lock on handle B.\n 4. It drops the TCP connection.\n\nThe result:\n\n - The handler thread blocks forever on r_count.\n - The kworker blocks forever in D state waiting on lock A, and lock A\n   is only freed by the file-table teardown inside terminate_fn.\n - The connection, session, files and locks leak.\n - Server stop or reset also hangs. stop_sessions() loops with\n   msleep(100) for as long as any connection remains in conn_list.\n\nWith two connections the same thing happens in a milder form: a\ndisconnected client\u0027s handler can\u0027t finish until some other client\nreleases its lock, which may never happen.\n\nThe fix breaks the cycle. cancel_fn is smb2_remove_blocked_lock(),\nwhich calls locks_delete_block() and wakes the waiter. The waiter then\nsees KSMBD_WORK_CANCELLED, cleans up and returns, r_count drops to zero,\nand teardown continues.\n\nOther async users at the fix\u0027s parent are not blockers:\n\n - CHANGE_NOTIFY (152036e875e8c) hands its pending state to a synthetic\n   in_work that holds a connection reference (owns_conn_ref), not\n   r_count. The original work returns right away.\n - Oplock and lease break waits are bounded by OPLOCK_WAIT_TIME.\n\nThe fix is patch 2/3 of a series \"in preparation for change notify\nsupport\". That notify design would sleep in a worker, but in the tree\ntoday the blocking lock is what triggers the bug.\n\n3. Tracing it back\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n0626e6641f6b4 is the root cause (v5.15-rc1~11^2~5^2~94,\nfs/cifsd/connection.c). This commit created everything involved in the\nbug:\n\n - conn-\u003easync_requests, conn-\u003er_count,\nKSMBD_WORK_ACTIVE/CANCELLED/CLOSED, and work-\u003ecancel_fn / cancel_argv\n(ksmbd_work.h).\n - The per-request atomic_inc(\u0026conn-\u003er_count) in queue_ksmbd_work() and\n   the matching dec at the end of handle_ksmbd_work() (server.c).\n - The teardown order, which never touches async_requests:\n\n    out:\n        /* Wait till all reference dropped to the Server object*/\n        while (atomic_read(\u0026conn-\u003er_count) \u003e 0)\n            schedule_timeout(HZ);\n        unload_nls(conn-\u003elocal_nls);\n        if (default_conn_ops.terminate_fn)\n            default_conn_ops.terminate_fn(conn);\n\nschedule_timeout() without setting the task state returns immediately,\nso this was actually a busy poll, but it was just as unbounded.\n\nThe fixed code still has this same sequence: the r_count wait, then\nterminate_fn. The fix inserts the missing cancel pass right before that\nwait.\n\nSame import series, needed for the bug to be reachable:\n\n - e2f34481b24db (\"cifsd: add server-side procedures for SMB3\", ~93)\n   added the blocking smb2_lock path (F_SETLKW|FL_SLEEP,\nFILE_LOCK_DEFERRED, setup_async_work(work, smb2_remove_blocked_lock,\nargv), sleep in ksmbd_vfs_posix_lock_wait()) and smb2_cancel(), the only\nplace that called cancel_fn.\n - f44158485826c (\"cifsd: add file operations\", ~92) added\n   ksmbd_vfs_posix_lock_wait() and set_close_state_blocked_works(). The\n   only teardown caller of the latter is ksmbd_destroy_file_table() via\n__close_file_table_ids(), which is reached only through terminate_fn.\nSo the waker was ordered after the wait from day one.\n\nAll three landed together in v5.15-rc1, so they share the same affected\nrange. The bug lives in the connection-lifecycle code that the fix\npatches, which is 0626e6641f6b4.\n\nLater commits that are not the root cause:\n\n - 45a64e8b08493 (\"ksmbd: uninterruptible wait for a file being\n   unlocked\", v5.15-rc1) changed wait_event_interruptible to wait_event.\n   Its changelog says \"the wait can be canceled by ...\ndisconnection\", but no disconnect path cancelled anything then. That\nmissing behaviour is exactly what 4fd5bad647bfa adds. kworkers don\u0027t get\nsignals, so the change made no practical difference.\n - a14c573870a66 (\"ksmbd: use wait_event instead of\n   schedule_timeout()\") turned the poll into wait_event(conn-\u003er_count_q,\n   ...). Same semantics, still unbounded.\n - abcc506a9a719 (v6.4-rc1) only added ksmbd_conn_set_releasing(conn)\n   before the wait.\n - A pickaxe search for async_requests found nothing between the\n   original import and the fix that cancels requests on disconnect.\nf580d27e89288 and 152036e875e8c only touch cancel and notify handling.\nThe blocked_works hits after the import are either after the fix\n(d12168084c8c1) or not on the disconnect path.\n\n4. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n - Root cause: 0626e6641f6b4 (\"cifsd: add server handler for central\n   processing and tranport layers\"). Its teardown waits for r_count \u003d\u003d 0\n   before terminate_fn and never cancels the work on\n   conn-\u003easync_requests. The blocking SMB2_LOCK path from e2f34481b24db\n   in the same series makes that wait permanent.\n - Suggested tag:\n     Fixes: 0626e6641f6b (\"cifsd: add server handler for central processing and tranport layers\")\n - Affected: v5.15 up to, but not including, v7.2.7. The fix first\n   appears in mainline in v7.3-rc1 and was backported to the 7.2.y\n   stable branch as 0947087678f46, first released in v7.2.7, so v7.2.12\n   is fixed. No release tag on any other stable branch contains the fix.\n - Related follow-ups: 528af7cf69f3e (\"smb/server: avoid registering\n   async requests during connection close\") is the next patch and closes\n   the race where new async work registers after the cancel pass.\n   4818df5aaac04 (\"ksmbd: wait for deferred notify cancellation\") is a\n   further follow-up.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "06c0386be9900758429a2931866d2663899b9b98",
      "tree": "bf2c4a58593105f5d6e4b68256c5a52895ad385e",
      "parents": [
        "3e92de5a6218f1000faecd040cd41c70d653ad7f"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 07:51:08 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98436: Add .vulnerable file\n\nRoot cause: ac49797c1815d4f8f04b7053b2998c546283c89e (\"accel/amdxdna:\nAdd GEM buffer object management\"), by Lizhi Hou, dated November 2024,\nfirst released in v6.14-rc1.\n\nThat commit created the device-buffer type (AMDXDNA_BO_DEV) and handed\nit to userspace. It never blocked dma-buf export for it, so the DRM\ncore\u0027s default export path applied from day one.\n\n1. What the fix does\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe fix, 9480dd7ec3fca (v7.3-rc1), adds an .export callback that always\nreturns -EOPNOTSUPP to amdxdna_gem_dev_obj_funcs, the function table\nused by AMDXDNA_BO_DEV objects. The commit message says only \"Device\nbuffers are never intended to be exported\". It has no Fixes: tag, and\nthe lore thread contains just the patch, Mario Limonciello\u0027s\nReviewed-by and an \"Applied to drm-misc-next\" note.\n\nBefore the fix, the table looked like this:\n\n    static const struct drm_gem_object_funcs amdxdna_gem_dev_obj_funcs \u003d {\n    \t.free \u003d amdxdna_gem_dev_obj_free,\n    \t.vmap \u003d amdxdna_gem_dev_obj_vmap,\n    };\n\nThere is no .export, and that gap is the bug.\n\n2. Why a missing .export means \"exportable\"\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nIn DRM, leaving .export NULL does not turn export off. It selects the\ngeneric exporter:\n\n - The fallback. export_and_register_object() in\n   drivers/gpu/drm/drm_prime.c:387-390 calls obj-\u003efuncs-\u003eexport if it is\n   set, and otherwise drm_gem_prime_export().\n\n - The ioctl is reachable. DRM_IOCTL_PRIME_HANDLE_TO_FD is declared\n   with only DRM_RENDER_ALLOW, so drm_ioctl_permit() lets any accel\n   client call it. Accel devices use DRM_ACCEL_FOPS, which routes\n   through drm_ioctl, and amdxdna never set prime_handle_to_fd.\n\n - The dma-buf pins the object. drm_gem_prime_export() calls\n   drm_gem_dmabuf_export(), which takes its own reference on the GEM\n   object and on the drm_device. That reference is only dropped in\n   drm_gem_dmabuf_release(), when the last dma-buf fd is closed.\n\nSo every GEM object amdxdna handed to userspace could be turned into a\ndma-buf fd. That fd keeps the object alive independently of the DRM file\nthat created it.\n\n3. What ac49797c1815d introduced\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe commit added the device-buffer type with these properties:\n\n - It is a sub-allocation of the creating client\u0027s heap.\namdxdna_drm_alloc_dev_bo() carves the BO out of abo-\u003edev_heap-\u003emm with\ndrm_mm_insert_node_generic(), under client-\u003emm_lock. It points mem.pages\ninto the heap\u0027s page array.\n\n - It holds a raw, uncounted pointer to the client. abo-\u003eclient \u003d\n   client is set at creation and dereferenced at free time.\n\n - It is a private object. It is set up with\n   drm_gem_private_object_init(), so it has no shmem file, no\n   get_sg_table and no pin.\n\n - Its function table has only a free hook. It is\n   { .free \u003d amdxdna_gem_obj_free }, with no .export.\n\n - It is published to userspace. amdxdna_drm_create_bo_ioctl() creates\n   a handle for AMDXDNA_BO_DEV like any other type.\n\n - The driver adds no PRIME hooks of its own. amdxdna_drm_drv sets\n   neither .prime_handle_to_fd nor .gem_prime_import, so both export and\n   import use the core defaults.\n\nThe free path assumes the client is still alive:\n\n    if (abo-\u003etype \u003d\u003d AMDXDNA_BO_DEV) {\n    \tmutex_lock(\u0026abo-\u003eclient-\u003emm_lock);\n    \tdrm_mm_remove_node(\u0026abo-\u003emm_node);\n    \tmutex_unlock(\u0026abo-\u003eclient-\u003emm_lock);\n    \t...\n\nClient teardown in amdxdna_drm_close() (the postclose hook) runs\nmutex_destroy(\u0026client-\u003emm_lock) and then kfree(client).\n\nThe design rests on one unwritten rule: a device BO dies before its\nclient. That holds only while the BO\u0027s references are handles in the\nclient\u0027s own drm_file. drm_gem_release() drops those handles before\npostclose runs. A dma-buf breaks the rule, and ac49797c1815d left that\ndoor open.\n\n4. How the bug plays out on ac49797c1815d\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. Open /dev/accel/accelN, create a DEV_HEAP BO, mmap it, then create\n    an AMDXDNA_BO_DEV BO.\n\n 2. Call DRM_IOCTL_PRIME_HANDLE_TO_FD on the device-BO handle. The\n    default drm_gem_prime_export() succeeds, and the dma-buf now holds\n    a reference on the device BO.\n\n 3. Close the DRM fd:\n     - drm_gem_release() drops the handle, but the BO survives because\n       the dma-buf still references it.\n     - amdxdna_drm_close() destroys client-\u003emm_lock and calls\n       kfree(client).\n\n 4. Close the dma-buf fd. drm_gem_dmabuf_release() drops the last\n    reference, which runs amdxdna_gem_obj_free(), which calls\n    mutex_lock(\u0026abo-\u003eclient-\u003emm_lock). That is a use-after-free of the\n    freed amdxdna_client.\n\nThe same export enables two more problems:\n\n - Cross-process sharing. Pass the dma-buf fd to another process that\n   has the same accel device open. The core drm_gem_prime_import_dev()\n   shortcut (dma_buf-\u003eops \u003d\u003d \u0026drm_gem_prime_dmabuf_ops \u0026\u0026 obj-\u003edev \u003d\u003d\n   dev) just takes another reference and gives that process a handle. It\n   now owns a BO that lives in another process\u0027s heap and address space,\n   and the handle also outlives the original client, leading to the same\n   use-after-free.\n\n - A user-triggerable WARN. Importing the dma-buf into another device\n   runs drm_gem_map_dma_buf(), which hits\nWARN_ON(!obj-\u003efuncs-\u003eget_sg_table) (drm_prime.c:676). With\npanic_on_warn set, that is a denial of service.\n\n5. Later history: nothing fixed it, several commits made it worse\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe amdxdna_gem_dev_obj_funcs table was checked at every commit that\ntouched it (git log -S hits only ac49797c1815d). No commit before the\nfix ever gave it an .export.\n\n - e486147c912f6 \"Add BO import and export\" (v6.16)\nAdded a custom .export only to amdxdna_gem_shmem_funcs, plus a custom\ngem_prime_import. Device BOs stayed on the default exporter.\nThe new import has no self-import shortcut, so importing a device-BO\ndma-buf into another amdxdna file now hits the get_sg_table WARN\ninstead. It also removed \"Add import and export BO support\" from the\ndriver TODO, although export had worked all along through the core\nfallback.\n\n - e252e3f3488a4 \"Revise device bo creation and free\" (v6.17)\nMoved the free path into amdxdna_gem_dev_obj_free(). Same dereference\nof abo-\u003eclient.\n\n - d76856beb4a4a (GEM refactor)\nAdded .vmap \u003d amdxdna_gem_dev_obj_vmap, which reads\nabo-\u003eclient-\u003edev_heap_xa. A dma-buf vmap after the client is gone\nbecomes another use-after-free.\n\n - dbc8fd7a03cbc \"Add expandable device heap support\"\n   amdxdna_gem_heap_free() now touches client-\u003emm_lock,\n   client-\u003edev_heap_mm, client-\u003eheap_usage and client-\u003edev_heap_xa.\namdxdna_client_cleanup() runs drm_mm_takedown(\u0026client-\u003edev_heap_mm) and\nkfree(client) while an exported device BO may still sit in that drm_mm.\n\n - 1f513a3ec3a94 \"per-process BO memory usage query\"\nFinally wrote the assumption down as a comment: \"DEV BOs cannot be\nalive when client is gone\" (amdxdna_gem.c:1225). Exporting violates\nexactly this.\n\nThese are refactors or widenings of the impact. None of them created\nthe exportability.\n\n6. Why ac49797c1815d and not e486147c912f6\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\ne486147c912f6 is the commit with \"export\" in its name, but it added\nexport for shmem BOs on purpose and never touched device BOs. Device\nBOs were already exportable, through drm_gem_prime_export(), from the\nmoment ac49797c1815d created them with a function table that had no\n.export. The fix adds exactly the missing entry, so the gap it closes\nis the one ac49797c1815d opened.\n\nResult: root cause is ac49797c1815d (\"accel/amdxdna: Add GEM buffer\nobject management\"), v6.14-rc1. Every kernel from v6.14 up to the fix in\nv7.3-rc1 is affected.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "3e92de5a6218f1000faecd040cd41c70d653ad7f",
      "tree": "f9076d18062a15397c0a8eb8cba5ae1ed0d04e43",
      "parents": [
        "2337152563231e33a3f6699b81493132eee803d2"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 07:45:10 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:13 2026 -0400"
      },
      "message": "CVE-2026-98428: Add .vulnerable file\n\nRoot cause: c6d43ba816d1c (\"ALSA: usb/6fire - Driver for TerraTec DMX\n6Fire USB\"), by Torsten Schenk, dated 2011-01-24, merged in v2.6.39-rc1.\n\nThe bug has been there since the driver was added. No later commit\nintroduced it, moved it or brought it back.\n\n1. What the fix commit 9fe49dbc023e8 changes (v7.3-rc3)\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nBefore the fix, the driver stored all of its URBs inside its own\nstructures instead of allocating them separately:\n\n - struct comm_runtime (comm.h)\n     Embedded URB: struct urb receiver; (interrupt-IN, resubmits itself\n                   forever)\n     Set up with:  usb_init_urb(\u0026rt-\u003ereceiver)\n     Freed by:     usb6fire_comm_destroy() -\u003e kfree(rt)\n\n - struct midi_runtime (midi.h)\n     Embedded URB: struct urb out_urb; (interrupt-OUT, MIDI output)\n     Set up with:  usb_init_urb() inside comm_rt-\u003einit_urb()\n     Freed by:     usb6fire_midi_destroy() -\u003e kfree(rt)\n\n - struct pcm_urb (pcm.h), held in the pcm_runtime arrays\n   in_urbs[PCM_N_URBS] and out_urbs[PCM_N_URBS]\n     Embedded URB: struct urb instance; followed by\n                   packets[PCM_N_PACKETS_PER_URB], marked\n                   \"BEGIN/END DO NOT SEPARATE\"\n     Set up with:  usb_init_urb(\u0026urb-\u003einstance)\n     Freed by:     usb6fire_pcm_destroy() -\u003e kfree(rt)\n\nThe fix turns each of these into a struct urb * allocated with\nusb_alloc_urb(). The PCM URBs are allocated with PCM_N_PACKETS_PER_URB\niso descriptors, so the separate packets[] array goes away and the code\nuses urb-\u003eiso_frame_desc[]. It frees them with usb_free_urb() in new\ncommon destructors (usb6fire_{comm,midi,pcm}_free()), which the probe\nerror paths and teardown both call. The commit message names the reason:\n\"since a URB is managed with a refcount, this may lead to a UAF when the\nURB is released asynchronously.\"\n\nIt is patch 4 of a series. Patches 2 and 3 are 07b01b0d8ac4b (ua101)\nand 7a8e247dab54a (hiface). The series came from Michal Pecio\u0027s lore\nmail \u003c20260903130757.0668310a.michal.pecio@gmail.com\u003e, written while\nlooking at a syzbot use-after-free in the caiaq driver, which uses the\nsame pattern. Key quotes from it:\n\n\"USB core continues to use a URB after completion to implement things\nlike usb_kill_urb(), so URBs are reference counted. Then core decrements\nthe count - one more use. If a driver waits for completion or even\nusb_kill_urb() to return and then proceeds to free the URB\u0027s storage,\nthis becomes a UAF.\"\n\n\"The only thing that works is for both USB and the driver to call\nusb_free_urb() and whoever does last will actually free the storage.\n  This means URB can\u0027t share storage with anything else.\"\n\n2. How the use-after-free happens\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe USB core side\n-----------------\n\nWhen a transfer completes, the USB core keeps touching the URB after\nthe driver\u0027s completion handler returns. On origin/master, in\n__usb_hcd_giveback_urb() (drivers/usb/core/hcd.c):\n\n    urb-\u003ecomplete(urb);                       // driver callback\n    usb_anchor_resume_wakeups(anchor);\n    atomic_dec(\u0026urb-\u003euse_count);              // (A) a killer/poisoner may now proceed\n    smp_mb__after_atomic();\n    if (unlikely(atomic_read(\u0026urb-\u003ereject)))  // (B) reads the URB again\n            wake_up(\u0026usb_kill_urb_queue);\n    usb_put_urb(urb);                         // (C) kref_put(\u0026urb-\u003ekref, ...) writes to the URB\n\nusb_kill_urb() and usb_poison_urb() (drivers/usb/core/urb.c) only wait\nfor use_count to reach zero:\n\n    usb_hcd_unlink_urb(urb, -ENOENT);\n    wait_event(usb_kill_urb_queue, atomic_read(\u0026urb-\u003euse_count) \u003d\u003d 0);\n\nSo the guarantee that usb_kill_urb()/usb_poison_urb() give is \"your\ncompletion handler is no longer running.\" It is NOT \"the core no longer\nreferences this memory.\" Steps (B) and (C) still happen after a waiter\ncan see use_count \u003d\u003d 0. Because wait_event() checks the condition before\nsleeping, and usb_kill_urb_queue is one global wait queue shared by\nevery URB in the system, the waiter does not need this particular\ngiveback\u0027s wake_up() to continue. It can return right after (A).\n\nThe reference counting is what makes this safe for normal URBs:\n\n - Submission does usb_get_urb(urb).\n - Giveback does usb_put_urb(urb) at (C).\n - The memory is only freed (urb_destroy() -\u003e kfree(urb)) when the last\n   reference is dropped.\n\nAn embedded URB gets no protection from this. Its storage belongs to\nthe containing kzalloc()\u0027d runtime struct, and the driver frees that\nstruct with kfree(), ignoring urb-\u003ekref.\n\nThe 6fire side, as written in c6d43ba816d1c\n-------------------------------------------\n\nThe original code, read from the introducing commit:\n\n - comm.c: usb_init_urb(urb) at lines 29 and 141, usb_submit_urb() at\n   154, usb_poison_urb(\u0026rt-\u003ereceiver) at 169, kfree(chip-\u003ecomm) at 174.\n - midi.c: usb_poison_urb(\u0026rt-\u003eout_urb) at 196, kfree(chip-\u003emidi) at\n   201.\n - pcm.c: usb_init_urb(\u0026urb-\u003einstance) at 590,\n   usb_poison_urb(\u0026rt-\u003e{in,out}_urbs[i].instance) at 678-679,\n   kfree(chip-\u003epcm) at 687.\n - chip.c: disconnect runs usb6fire_chip_abort() (poison everything)\n   and then usb6fire_chip_destroy() (kfree every runtime).\n\nThe race on disconnect, using the PCM in-URB as the example (comm and\nMIDI are the same):\n\n    CPU0: HCD giveback (BH/softirq)            CPU1: usb6fire_chip_disconnect()\n    ----------------------------------------   ------------------------------------\n    __usb_hcd_giveback_urb(\u0026rt-\u003ein_urbs[i].instance)\n      urb-\u003ecomplete() -\u003e usb6fire_pcm_in_urb_handler()\n         (status !\u003d 0 -\u003e returns)\n      atomic_dec(\u0026urb-\u003euse_count)   -- (A)\n      \u003cpreempted / delayed: PREEMPT_RT,\n       vCPU preemption, irq, ...\u003e\n                                               usb6fire_pcm_abort()\n                                                 usb_poison_urb(\u0026...instance)\n                                                   wait_event(use_count \u003d\u003d 0) -\u003e passes at once\n                                               ... snd_card_free_when_closed(): no\n                                                   one has the card open, so it is freed now\n                                               -\u003e usb6fire_pcm_destroy()\n                                                   kfree(rt)  \u003c- the URB was inside rt\n      atomic_read(\u0026urb-\u003ereject)     -- (B) UAF read\n      wake_up(\u0026usb_kill_urb_queue)\n      usb_put_urb(urb) -\u003e kref_put  -- (C) UAF write (atomic decrement in freed slab memory)\n\nWhat (C) does to memory:\n\n - usb_init_urb() set the kref to 1 and submission raised it to 2, so\n   the late kref_put() decrements a counter inside a freed (and maybe\n   already reused) pcm_runtime/comm_runtime/midi_runtime. That corrupts\n   whatever object now lives at that offset.\n - If the reused memory happens to read as 1 at that offset, the\n   decrement reaches 0. urb_destroy() then calls kfree() on a pointer\n   into the middle of an unrelated object, which is an invalid or double\n   free.\n\nThe comm receiver URB is always active, because it resubmits itself\nfrom its completion handler. That means this window exists on every\ndisconnect, and in the probe error paths whenever the receiver was\nsubmitted before a later init step failed.\n\nA second problem in the PCM layout\n----------------------------------\n\nThe \"DO NOT SEPARATE\" comment in struct pcm_urb relied on struct urb\u0027s\ntrailing flexible array iso_frame_desc[] running into the following\npackets[] member. That only works for an embedded URB, and it places a\nflexible-array member in the middle of a struct, which the kernel now\ntreats as deprecated (Michal points out the same thing). The fix removes\nboth problems by using usb_alloc_urb(PCM_N_PACKETS_PER_URB,\n...).\n\n3. Why c6d43ba816d1c is the origin \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\n 1. All three embedded URBs and the usb_init_urb/kfree pattern were in\n    the driver\u0027s first version. git log origin/master -S for each of\n    \"struct urb receiver\", \"struct urb out_urb\", \"struct urb instance\"\n    and \"usb_init_urb\" under sound/usb/6fire/ matches exactly two\n    commits: c6d43ba816d1c (adds them) and 9fe49dbc023e8 (the fix). No\n    refactor in between changed how these URBs are stored.\n\n 2. The USB core already behaved this way in 2011. In the c6d43ba816d1c\n    tree, usb_hcd_giveback_urb() already did\n\n        urb-\u003ecomplete(urb);\n        atomic_dec(\u0026urb-\u003euse_count);\n        if (atomic_read(\u0026urb-\u003ereject))\n                wake_up(...);\n        usb_put_urb(urb);\n\n    and submission already did usb_get_urb(urb). The race window did\n    not come from a later USB core change. The driver was racy from the\n    day it was merged.\n\n 3. The documentation at the time already warned about it. The\n    kernel-doc for usb_init_urb() in that tree says:\n\n      \"Only use this if you allocate the space for a struct urb on your\n      own. If you call this function, be careful when freeing the\n      memory for your urb that it is no longer in use by the USB core.\n      Only use this function if you _really_ understand what you are\n      doing.\"\n\n    Documentation/usb/URB.txt section 1.3 says \"URBs are allocated with\n    ... usb_alloc_urb()\". c6d43ba816d1c broke exactly that rule: it\n    frees the containing struct as soon as poison returns, without\n    waiting for the core\u0027s last usb_put_urb().\n\n 4. Later commits only changed the surrounding code:\n\n     - ddb6b5a964371, 5ece263f1d93f and 4c2aee0032b70 (v3.11) moved the\n       transfer buffers out of the structs to make them DMA-able. The\n       URBs themselves stayed embedded.\n     - a0810c3d6dd2d (v6.13, \"Release resources at card release\")\n       moved the kfree(rt) calls into the card\u0027s private_free. This\n       changed when the free happens. The free is still immediate when\n       no one has the card open, so the race remains.\n     - b9c826916fdce (v7.1-rc1) and a54bf16965f89 (v7.2-rc6) fixed\n       other use-after-frees: chip/card lifetime on disconnect and the\n       probe error path. Neither touched how the URBs are stored or\n       refcounted.\n\n4. Conclusion \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n\nThe use-after-free comes from storing reference-counted struct urb\nobjects inside driver-owned memory (comm_runtime.receiver,\nmidi_runtime.out_urb, pcm_urb.instance with its packets[]) and freeing\nthat memory with kfree() as soon as usb_poison_urb()/usb_kill_urb()\nreturn. That ignores the USB core\u0027s last read and usb_put_urb() in the\ngiveback path. The driver introduced this design in its very first\ncommit, c6d43ba816d1c, so that is the right Fixes: tag and the start of\nthe affected range: every kernel since v2.6.39 that builds\nCONFIG_SND_USB_6FIRE, up to 9fe49dbc023e8 (v7.3-rc3).\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "2337152563231e33a3f6699b81493132eee803d2",
      "tree": "1b8aa56a049ea7d0231d1c6d58e28db84c323b73",
      "parents": [
        "178939a30eb25ea74631f85b3934daffefd69296"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 07:38:08 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:12 2026 -0400"
      },
      "message": "CVE-2026-98389: Add .vulnerable file\n\nRoot cause: 8fdb3958e396 (\"drm/amdgpu/gfx: add ring helpers for setting\nworkload profile\") by Alex Deucher, dated 2025-01-08 and first released\nin v6.15-rc1. Three commits from the same series wire it into gfx10,\ngfx11 and gfx12 hardware, and all three are also in v6.15-rc1. The fix\n1f1d43418d61 (\"drm/amdgpu: skip gfx switch_power_profile during GPU\nreset\") is in v7.3-rc3. It has Cc: stable but no Fixes: tag. The correct\ntag would be:\n\n\tFixes: 8fdb3958e396 (\"drm/amdgpu/gfx: add ring helpers for setting workload profile\")\n\n8fdb3958e396 was not backported to 6.12.y, 6.6.y, 6.1.y, 5.15.y or\n5.10.y, so those branches are not affected.\n\n1. What the fix does\n--------------------\n\nThe fix adds an amdgpu_in_reset(adev) early return to two functions in\ndrivers/gpu/drm/amd/pm/amdgpu_dpm.c:\n\n - amdgpu_dpm_switch_power_profile()\n - amdgpu_dpm_pause_power_profile()\n\nAs a result, any profile switch requested while a GPU reset is running\nis a no-op. The trace in the fix\u0027s commit message shows where the bad\nrequest comes from:\n\n\tamdgpu_gfx_profile_idle_work_handler      \u003c- kworker (system_wq)\n\t amdgpu_dpm_switch_power_profile\n\t  smu_switch_power_profile\n\t   smu_bump_power_profile_mode\n\t    smu_v13_0_0_set_power_profile_mode    (gfx11 / Navi3x)\n\t     smu_cmn_send_smc_msg_with_param -\u003e smu_msg_v1_send_msg\n\t      amdgpu_device_rreg                  \u003c- assertion fires here\n\n2. Why the assertion fires\n--------------------------\n\namdgpu_device_rreg() first calls amdgpu_device_skip_hw_access(). With\nCONFIG_LOCKDEP, that function checks that no reset is running in\nparallel:\n\n\tif (in_task()) {\n\t\tif (down_read_trylock(\u0026adev-\u003ereset_domain-\u003esem))\n\t\t\tup_read(\u0026adev-\u003ereset_domain-\u003esem);\n\t\telse\n\t\t\tlockdep_assert_held(\u0026adev-\u003ereset_domain-\u003esem);\n\t}\n\nThe reset thread starts with amdgpu_device_lock_reset_domain(). That\nsets in_gpu_reset \u003d 1 and takes down_write(\u0026reset_domain-\u003esem). Both are\nreleased only in amdgpu_device_unlock_reset_domain(), at the very end of\nrecovery.\n\nA kworker running the gfx idle work does not hold the semaphore, so its\ntrylock fails and lockdep_assert_held() fires. The warning points at a\nreal hazard: the SMU mailbox is being driven from a second thread while\nthe ASIC is still being re-initialised.\n\nThe check was added by 56b53c0b5aa5 (\"drm/amdgpu: add codes to capture\ninvalid hardware access when recovery\") in v5.13. That commit only\ndetects the problem. It is not the cause.\n\n3. How the race happens\n-----------------------\n\namdgpu_ring_alloc() calls ring-\u003efuncs-\u003ebegin_use(), and\namdgpu_ring_commit() and amdgpu_ring_undo() call end_use(). Since the\ngfx wiring commits, gfx10/11/12 point those hooks at\namdgpu_gfx_profile_ring_begin_use() and\namdgpu_gfx_profile_ring_end_use(). On a full reset the sequence is:\n\n 1. amdgpu_device_gpu_recover() takes the reset lock (in_gpu_reset \u003d 1\n    and sem held for write).\n\n 2. During IP suspend, gfx_v11_0_hw_fini() calls\n    cancel_delayed_work_sync(\u0026adev-\u003egfx.idle_work), so the idle work is\n    stopped at this point.\n\n 3. amdgpu_device_reinit_after_reset() then runs:\n\n    - ip_resume_phase2 runs the gfx hw_init and the KGQ/KCQ ring tests.\n      Each test calls amdgpu_ring_alloc(), which calls begin_use and\n      switches the profile on from the reset thread itself. That is\n      allowed because this thread holds the semaphore.\n\n    - Each test\u0027s amdgpu_ring_commit() calls end_use, which re-arms\n      idle_work through schedule_delayed_work(...,\n      GFX_PROFILE_IDLE_TIMEOUT). The timeout is one second.\n\n    - The reset keeps running for much longer than that: phase3,\n      amdgpu_device_ip_late_init(), userq post-reset, RAS resume, XGMI,\n      and then amdgpu_ib_ring_tests(). The IB tests re-arm the idle\n      work again.\n\n 4. One second after the last end_use, the idle work runs in a kworker.\n    The test fences have signalled and total_submission_cnt is 0, so it\n    calls amdgpu_dpm_switch_power_profile(adev, profile, false). That\n    sends an SMU message, which reads a register while the reset thread\n    still holds sem. The result is the assertion above, plus SMU access\n    during reset with no synchronisation.\n\nNothing on this path ever coordinated with reset:\n\n - the idle handler neither checks amdgpu_in_reset() nor takes\n   down_read_trylock(\u0026adev-\u003ereset_domain-\u003esem);\n - end_use re-arms the work even when called from the reset thread;\n - amdgpu_dpm_switch_power_profile() only skipped SR-IOV VFs.\n\nOther code in amdgpu_gfx.c does use the trylock guard, for example the\npartition-mode store and the MES remove_hw_queue path.\n\n4. History\n----------\n\namdgpu_dpm_switch_power_profile() never checked for reset at any point\n(git log -L):\n\n - 9530273ec90c: powerplay API wrapper\n - 7cf7a392af26: SR-IOV skip\n - bab0f602918d, 3712e7a49459: API and locking refactors\n - 1f1d43418d61: the fix, the first reset check\n\nIt is only a dispatcher. Before 2025 its callers were the VCN/JPEG idle\nworkers and KFD compute-idle, so it was not on the gfx path in this\nreport.\n\namdgpu_gfx_profile_idle_work_handler() has only two commits in its\nhistory:\n\n - 8fdb3958e396 created it, together with\n   amdgpu_gfx_profile_ring_begin_use/end_use, the gfx.idle_work delayed\n   work and GFX_PROFILE_IDLE_TIMEOUT (1 s). As first written, the\n   handler counted emitted fences and then called\n   amdgpu_dpm_switch_power_profile(adev, profile, false) from the\n   workqueue, with no reset check. end_use scheduled the work on every\n   call, so the reset thread\u0027s ring tests armed it as well. The commit\n   message says it \"works the same as the VIDEO profile for VCN\". The\n   deferred-switch pattern was copied without adding any reset handling.\n\n - 553673a3e1bc (\"drm/amdgpu/gfx: fix ref counting for ring based\n   profile handling\"), tagged Fixes: 8fdb3958e396, added\n   workload_profile_mutex and workload_profile_active around the same\n   call. That only balanced the reference counts and did nothing about\n   reset.\n\nLater changes kept the same flaw:\n\n - 9e34d8d1a1ab made begin_use independent of the result of cancelling\n   the work.\n - 5cccf10f6521 added an overdrive bail-out.\n - 50be7c9b5d5e (2026-06) changed end_use to re-arm only when the count\n   drops to zero. The ring tests still bring the count to zero, so the\n   work is still armed during reset.\n\nThe wiring commits, all in v6.15-rc1, are what make the handler run on\nreal hardware:\n\n - b9467983b774: gfx10\n - 963537ca2325: gfx11. This is the generation in the reported trace\n(SMU 13.0.0). It added INIT_DELAYED_WORK in sw_init, the cancel in\nhw_fini, and gfx_v11_0_ring_begin_use/end_use on the gfx and compute\nrings.\n - 5f95a1549555: gfx12\n\nThese commits only connect the hooks. The defective logic is in\n8fdb3958e396, and all four commits landed together in v6.15-rc1, so the\naffected range is the same whichever one is chosen.\n\n5. Conclusion -------------\n\nEvery kernel from v6.15-rc1 onward running on gfx10, gfx11 or gfx12\nhardware is affected, up to the fix in v7.3-rc3.\n\nTwo smaller points:\n\n - The pause_power_profile half of the fix is defensive only.\namdgpu_dpm_pause_power_profile() (added in 7f991dd3641e) has no callers\nin mainline, because its display-code users were reverted.\n - The VCN idle worker, where this pattern came from, uses the same\n   deferred design. That is a separate code path and not what this fix\n   targets.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "178939a30eb25ea74631f85b3934daffefd69296",
      "tree": "3fd702bd101b6f99199f44f6c782fbb1b7af7431",
      "parents": [
        "d481bcea90fe4f544a308bb09f986114f55cf1f4"
      ],
      "author": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sat Oct 24 07:27:32 2026 -0400"
      },
      "committer": {
        "name": "Sasha Levin",
        "email": "sashal@kernel.org",
        "time": "Sun Oct 25 21:10:12 2026 -0400"
      },
      "message": "CVE-2023-7356: Add .vulnerable file\n\nRoot cause: 9c0826310bfb (\"Bluetooth: ISO: Add support for periodic adv\nreports processing\") by Claudia Draghicescu, merged in v6.6-rc1. The fix\nf4da3ee15de9 landed in v6.7-rc1, so v6.6 shipped with the bug. The fix\ncarries no Fixes: tag; the correct one would be:\n\n\tFixes: 9c0826310bfb (\"Bluetooth: ISO: Add support for periodic adv reports processing\")\n\n9c0826310bfb was also backported to 6.1.y as e4cfffb5ebc8 (6.1.159), so\nwith this .vulnerable file dyad resolves the ranges to:\n\n\t6.1.159 - 6.1.177  (fixed in 6.1.178 by ba25efe15e8f)\n\t6.6     - 6.6.144  (fixed in 6.6.145 by 927ef409f713)\n\t6.6     - 6.7-rc   (fixed in 6.7 by f4da3ee15de9)\n\n1. What the fix changes\n-----------------------\n\nIn iso_connect_ind(), the HCI LE Periodic Advertising Report branch\nused to do:\n\n\tif (sk) {\n\t\tmemcpy(iso_pi(sk)-\u003ebase, ev3-\u003edata, ev3-\u003elength);\n\t\tiso_pi(sk)-\u003ebase_len \u003d ev3-\u003elength;\n\t}\n\nThe fix replaces it with:\n\n\tbase \u003d eir_get_service_data(ev3-\u003edata, ev3-\u003elength,\n\t\t\t\t    EIR_BAA_SERVICE_UUID, \u0026base_len); /* 0x1851 */\n\tif (base \u0026\u0026 sk \u0026\u0026 base_len \u003c\u003d sizeof(iso_pi(sk)-\u003ebase)) {\n\t\tmemcpy(iso_pi(sk)-\u003ebase, base, base_len);\n\t\tiso_pi(sk)-\u003ebase_len \u003d base_len;\n\t}\n\nIt also adds put_user(len, optlen) to the BT_ISO_BASE getsockopt path.\nThat second part is minor and is covered in section 6.\n\nSo the fix does three things:\n\n - It stops treating the whole advertising payload as the BASE. The\n   BASE (Broadcast Audio Source Endpoint) is only the payload of the\n   Service Data AD structure whose UUID16 is 0x1851 (Basic Audio\n   Announcement).\n - It only stores something when that AD structure is actually present.\n - It bounds the copy to sizeof(iso_pi(sk)-\u003ebase).\n\n2. The buffer was always meant to hold only the BASE\n----------------------------------------------------\n\nbase[] and its size come from f764a6c2c1e4 (\"Bluetooth: ISO: Add\nbroadcast support\", v6.0-rc1):\n\n\t#define EIR_SERVICE_DATA_LENGTH 4\n\t#define BASE_MAX_LENGTH (HCI_MAX_PER_AD_LENGTH - EIR_SERVICE_DATA_LENGTH) /* 252 - 4 \u003d 248 */\n\t...\n\t\t__u8\t\t\tbase_len;\n\t\t__u8\t\t\tbase[BASE_MAX_LENGTH];\n\t\tstruct iso_conn\t\t*conn;\n\nThe size says what the buffer holds: the maximum periodic advertising\ndata length minus the 4-byte Service Data header (1 byte length, 1 byte\nAD type 0x16, 2 bytes UUID16). In other words, base[] holds the\nservice-data payload, not the raw report.\n\nIn that commit, nothing from the air ever went into base[]:\n\n - base[] was only filled by setsockopt(BT_ISO_BASE), which rejected\n   anything larger than sizeof(base) with -EOVERFLOW.\n - getsockopt(BT_ISO_BASE) on a connected socket returned\n   hcon-\u003ele_per_adv_data, which is the broadcaster\u0027s own advertising\n   data.\n\nSo before v6.6 there was no path where controller data entered base[].\n\n3. How 9c0826310bfb introduced the bug\n--------------------------------------\n\nThat commit added broadcast-sink support for receiving the BASE from a\nremote broadcaster:\n\n 1. hci.h: it added struct hci_ev_le_per_adv_report, with\n    \"__u8 length; __u8 data[];\".\n\n 2. hci_event.c: it added hci_le_per_adv_report_evt(), registered with\n    HCI_LE_EV_VL(..., sizeof(struct hci_ev_le_per_adv_report),\n    HCI_MAX_EVENT_SIZE). The generic hci_le_meta_evt() only checks\n    skb-\u003elen \u003e\u003d 7, and an oversized event only produces a warning.\n    Nobody checks ev-\u003elength against skb-\u003elen or against any\n    destination size. The handler calls\n    hci_proto_connect_ind(hdev, BDADDR_ANY, ISO_LINK, \u0026flags).\n\n 3. iso.c, iso_connect_ind(): it added the ev3 branch. This branch\n    gets the event through hci_recv_event_data(), which points into\n    hdev-\u003erecv_event, an skb_clone() taken at the top of\n    hci_event_packet(). It looks up the listening socket by\n    sync_handle and does the unchecked\n    memcpy(iso_pi(sk)-\u003ebase, ev3-\u003edata, ev3-\u003elength).\n\n 4. iso.c, iso_sock_getsockopt(BT_ISO_BASE): it changed the condition\n    to \"sk_state \u003d\u003d BT_CONNECTED \u0026\u0026 !bacmp(dst, BDADDR_ANY)\".\n    Broadcast receivers now get iso_pi(sk)-\u003ebase back.\n\n 5. iso.c, iso_conn_ready(): it copies the parent\u0027s base/base_len into\n    each accepted child BIS socket, so the bad contents spread to\n    child sockets.\n\nEnd-to-end flow:\n\n 1. A remote broadcaster sends periodic advertising over the air.\n 2. The controller emits LE Meta subevent 0x0f, the Periodic\n    Advertising Report.\n 3. hci_event_packet() stores the clone in hdev-\u003erecv_event, and\n    hci_le_meta_evt() dispatches to hci_le_per_adv_report_evt().\n 4. That handler calls hci_proto_connect_ind(), which reaches\n    iso_connect_ind().\n 5. iso_connect_ind() finds the PA report, matches the listening\n    socket by sync_handle, and copies ev3-\u003elength bytes from\n    ev3-\u003edata into iso_pi(sk)-\u003ebase.\n 6. Userspace calls getsockopt(BT_ISO_BASE) and receives that\n    content. Child BIS sockets inherit it in iso_conn_ready().\n\n4. The two defects in that memcpy\n---------------------------------\n\n(a) Wrong data: the whole report is treated as the BASE\n\nThe periodic advertising payload is a sequence of AD structures. It can\ncontain Flags, Broadcast Name, other service data, and the\n0x16/0x1851 Basic Audio Announcement. With Data_Status, it can also be\njust a fragment.\n\n9c0826310bfb copies all of it, including the 4-byte AD header that\nBASE_MAX_LENGTH explicitly subtracts. A broadcast sink calling\ngetsockopt(BT_ISO_BASE) gets AD-encoded bytes instead of a BASE.\nUserspace (BlueZ/BAP) then misparses codec, subgroup and BIS\nconfiguration. If the report has no Basic Audio Announcement at all,\nwhatever junk is there still overwrites a previously valid BASE. The\nfix\u0027s eir_get_service_data(..., 0x1851, ...) addresses exactly this.\nThat helper already existed since 8f9ae5b3ae80 (\"Bluetooth: eir: Add\nhelpers for managing service data\"), so it was available to\n9c0826310bfb.\n\n(b) Memory safety: the length is never checked\n\nev3-\u003elength is a __u8 taken straight from the controller, so it can be\n0 to 255. The destination is base[248]. The spec caps Data_Length at\n247, but the kernel never enforces that, and a malicious, buggy or\nemulated controller (for example over USB) can send any value.\n\nstruct iso_pinfo layout at 9c0826310bfb (X \u003d sizeof(struct bt_sock),\nwhich is 8-aligned):\n\n\tField\t\t\tOffset\n\tsrc\t\t\tX+0\n\tbc_bis[31]\t\tX+16\n\tsync_handle\t\tX+48\n\tflags\t\t\tX+56\n\tqos (60 bytes)\t\tX+64\n\tqos_user_set\t\tX+124\n\tbase_len\t\tX+125\n\tbase[248]\t\tX+126 to X+373\n\tpadding\t\t\tX+374 to X+375\n\tconn\t\t\tX+376\n\nA 255-byte copy ends at X+380. It overwrites the 2 padding bytes and\nthen the low 5 bytes of the iso_pi(sk)-\u003econn pointer, which is later\ndereferenced. That is kernel heap corruption of a pointer, triggered by\ncontroller input.\n\nThere is also an out-of-bounds read. HCI event parameters are at most\n255 bytes, so after the subevent byte and the 7 fixed bytes, at most\n247 data bytes are really present. Any length larger than the bytes\nactually in the skb makes the memcpy read past the event payload, and\nthose stale bytes are then handed to userspace by\ngetsockopt(BT_ISO_BASE).\n\nThe fix\u0027s \"base_len \u003c\u003d sizeof(iso_pi(sk)-\u003ebase)\" check closes the\noverflow.\n\n5. Why it is not older\n----------------------\n\n - Before 9c0826310bfb, iso_connect_ind() had no\nHCI_EV_LE_PER_ADV_REPORT handling, the report struct and event handler\ndid not exist, and nothing from the controller was ever copied into\nbase[].\n - The buffer and its BASE-only size (f764a6c2c1e4) were correct as\n   designed: the only writer was the bounded setsockopt.\n - The flawed copy appears in 9c0826310bfb itself, not through any\n   later move or refactor. The fix rewrites exactly those lines.\n\n6. The secondary part of the fix\n--------------------------------\n\nThe added put_user(len, optlen) fixes a separate, minor problem.\ngetsockopt(BT_ISO_BASE) never reported how many bytes it returned, so\nuserspace could not tell the BASE length. That code dates back to\nf764a6c2c1e4 (v6.0-rc1). It is not the memory-safety or wrong-BASE issue\nthe CVE and the commit subject are about. Those both come from\n9c0826310bfb.\n\nSigned-off-by: Sasha Levin \u003csashal@kernel.org\u003e\n"
    },
    {
      "commit": "d481bcea90fe4f544a308bb09f986114f55cf1f4",
      "tree": "fe1cfa5987e674f69993b0581d2e6fb1a8c27148",
      "parents": [
        "a2d020f204e604a0d7ed9f9019b0017de70c7213"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sun Oct 25 18:19:15 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sun Oct 25 18:19:15 2026 +0100"
      },
      "message": "cve_utils: drop some extra blank lines\n\nForgotten in code movement\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "a2d020f204e604a0d7ed9f9019b0017de70c7213",
      "tree": "d93e92af604e1db4179bdd5a2e47254e1c669222",
      "parents": [
        "0dc281b20b9cca23a16fb8b46cdb78dea0a9b58d"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sun Oct 25 18:16:07 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sun Oct 25 18:16:07 2026 +0100"
      },
      "message": "update_dyad: fix the text message up a bit\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "0dc281b20b9cca23a16fb8b46cdb78dea0a9b58d",
      "tree": "526ec442cf619b0503398cd89d797dee6368d491",
      "parents": [
        "ae949385bbfbffaf42da032312f1072c95fa9db1"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sun Oct 25 18:01:49 2026 +0100"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sun Oct 25 18:01:49 2026 +0100"
      },
      "message": "cve_utils: move the \"parse the command line\" logic to cve_utils\n\nBoth update_dyad and cve_update use the same logic to determine what to\nupdate (a year, all years, or a specific id), so move that logic from\nupdate_dyad into cve_utils so that cve_update can use it in the future.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "ae949385bbfbffaf42da032312f1072c95fa9db1",
      "tree": "38bb4d5f75abec8c1e48a057fa6899be854e858e",
      "parents": [
        "34929df133ad07c180098a6b7d5b221a79b0b028"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:53:46 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:53:59 2026 +0200"
      },
      "message": "cve_utils: cache the prepared verhaal statements per connection\n\nEvery verhaal query re-prepared its SQL from scratch, and the handful of\nquery strings are all fixed, so a full cve_update run parsed and planned\nthe same few statements hundreds of thousands of times -\nsqlite3_prepare_v3() accounted for about a tenth of the tool\u0027s own CPU\nsamples.\n\nUse rusqlite\u0027s per-connection statement cache instead: prepare_cached()\nhands back the already-compiled statement on every call after the first,\nand the connections are already cached per thread so the cache stays hot\nfor the whole run.  sqlite3_prepare_v3() drops to noise level in the\nprofile.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "34929df133ad07c180098a6b7d5b221a79b0b028",
      "tree": "0a7d0cad3fa6a6c7fd8bda99f5c50331cb8d9f28",
      "parents": [
        "969f22ae48682b555e7bfa45bdceb2b5511216ce"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:53:09 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:53:24 2026 +0200"
      },
      "message": "cve_utils: compute the affected-files lists in bulk instead of per CVE\n\nget_affected_files() forks \u0027git show --name-only\u0027 once per commit, and\nwith the engine running in-process that fork had become the single\nlargest cost of a cve_update run: 18000 process spawns accounting for\nabout half of the total CPU time.  (Computing the lists with libgit2\u0027s\ntree diff instead was tried and is two orders of magnitude slower, as it\ndecompresses large parts of the kernel pack files per commit.)\n\nOne git process can answer for thousands of commits though, so add\nprime_affected_files_cache(): it feeds the commit ids to a handful of\nconcurrent \u0027git diff-tree -r -M --name-only --stdin\u0027 processes and\ncaches the parsed lists in a process-wide map that get_affected_files()\nconsults first.  The -M flag matches the rename detection git show\nperforms by default, and a full cve_update run over all published CVEs\nconfirms the lists are identical.  Anything not in the cache (e.g. a\nsingle-CVE bippy run) keeps using the per-commit fallback unchanged.\n\ncve_update primes the cache once per year before its workers start.  A\nfull run drops from ~2.1s to ~1.3s wall clock, with total CPU time down\nfrom ~52s to ~14s.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "969f22ae48682b555e7bfa45bdceb2b5511216ce",
      "tree": "46ac2f8c6104136d7267601f737c328b3695d526",
      "parents": [
        "5fc73baf71a859a0b380af30b51653b2f47e3ec5"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:52:27 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:52:27 2026 +0200"
      },
      "message": "cve_utils: look up the bippy engine\u0027s fixed inputs once per process\n\nEvery record the bippy engine generated re-resolved inputs that cannot\nchange while a tool runs: the kernel tree path, the scripts directory (a\ndirectory-probing walk from the current directory), the scripts/tags\nlist and the CVE team UUID in scripts/linux.uuid.  A full cve_update run\nrepeated each of those lookups 18000 times.\n\nResolve each of them once per process into a OnceLock, following the\npattern the dyad engine already uses for its whitelist files.\nprocess_commit_text() now takes the tag list instead of the directory to\nread it from, so the one caller that cares (the engine) can hand it the\ncached list.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "5fc73baf71a859a0b380af30b51653b2f47e3ec5",
      "tree": "6f4534f626136e93e437769965d8f6308fbf9146",
      "parents": [
        "d1dff5370e7eb9ce49f37e81bf45b035ff86a02a"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 09:55:41 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:51:57 2026 +0200"
      },
      "message": "cve_utils: open the kernel repository through the per-thread cache in bippy_core\n\nThe bippy engine opened a fresh libgit2 Repository handle for every\nrecord it generated.  Opening a repository is not free (discovery,\nconfig parsing, object database setup), and cve_update now generates\n18000 records per run in-process, so route the lookup through\ngit_utils::with_repository() like the rest of the tools: the handle is\nopened once per thread and reused for every later record.\n\nThe dyad engine runs outside the with_repository() closure, so the\nper-thread cache is never re-entered.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "d1dff5370e7eb9ce49f37e81bf45b035ff86a02a",
      "tree": "e92f0e546df6ed3bb8387fd8a51b13d9535db8ba",
      "parents": [
        "256fee7c9e70099c08f897fea12720ebedd8c694"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:50:50 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:50:50 2026 +0200"
      },
      "message": "cve_update.rs: run the bippy engine in-process instead of forking bippy\n\nUpdating the published CVE entries forked four processes per CVE: one\nbippy (which re-validated the environment, re-opened the kernel\nrepository and re-read the tags, uuid and whitelist files every time),\nand one diff for each of the .json and .mbox comparisons, plus the\ntemporary files the results were shuttled through.  Across 18000\npublished CVEs that is over 50000 process spawns to find out that\nusually nothing changed.\n\nCall cve_utils::bippy_core::generate() directly instead and compare the\nreturned strings against the existing files in memory.  The comparison\nignores the same lines the old diff-based check ignored (the generator\nversion and the user email lines), so the update decisions are\nunchanged, and the written files are byte-identical to what the forked\nbippy produced.  The user identity is resolved once per run instead of\nonce per CVE, and a missing CVE_USER or CVEKERNELTREE is now reported\nonce at startup instead of failing every single CVE with the same\nmessage.\n\nA full cve_update run drops from ~7.9s to ~2.3s wall clock, with total\nCPU time down from ~228s to ~54s.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "256fee7c9e70099c08f897fea12720ebedd8c694",
      "tree": "6887feb9e3ec87f4fe368ac413c762aca0bac506",
      "parents": [
        "1b944ed5a369ebb98bc5ebfcca07d9f3d869369f"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:49:40 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:49:55 2026 +0200"
      },
      "message": "bippy: move the record-generation engine into cve_utils as bippy_core\n\ncve_update forks one bippy process per published CVE, and each of those\nforks re-does from scratch work whose results never change during a run.\nTo let it call the engine in-process the same way update_dyad calls\ndyad_core, move everything between argument parsing and writing the\noutput files into the library as cve_utils::bippy_core, with a\ngenerate() entry point that takes the per-CVE inputs and returns the\nrendered .json and .mbox contents as strings.\n\nThe bippy binary becomes a thin command line front end: it parses and\nvalidates the arguments exactly as before, calls the engine, and writes\nthe files.  The generated output is byte-identical for all published\nCVEs.  The only behavioral differences are confined to failure paths:\nerrors that used to be logged right before a process::exit(1) now\ntravel out of the engine as errors and are logged by the binary, with\nthe same non-zero exit code.\n\nThe engine modules keep their layout (policy, version, json, mbox, cve\nschema, text, file, git, dyad), now under cve_utils/src/bippy_core/.\nThe consistency test can finally import FIRST_LINUX_COMMIT from the\nlibrary instead of mirroring the constant.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "1b944ed5a369ebb98bc5ebfcca07d9f3d869369f",
      "tree": "acb44f43973050b0f2e1cd921452c73e9cc95530",
      "parents": [
        "8843e3865d67ee9e0e3da37c040778dc4a1b40a8"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 09:39:46 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:46:39 2026 +0200"
      },
      "message": "bippy: run the dyad engine in-process instead of forking scripts/dyad\n\nEvery bippy invocation forked the dyad binary to compute the\nvulnerable:fixed kernel pairs, so each run paid for a process spawn plus\ndyad\u0027s startup work (opening the kernel repositories, the verhaal\ndatabase and the whitelist files) all over again.  The fork also\ntemporarily chdir()ed the whole process to the scripts directory, which\nis process-global state that would make the engine unusable from\nmultiple threads.\n\nThe dyad engine already lives in the library as cve_utils::dyad_core\nexactly so that tools do not have to fork one dyad process per CVE, so\ncall it directly.  format_output() returns the same canonical text the\ndyad tool prints, so the parsing of the result is unchanged, and the\ngenerated .json/.mbox files are byte-identical for all published CVEs.\n\nA full cve_update run (which forks bippy once per CVE) drops from ~9.5s\nto ~7.9s wall clock, with total CPU time down from ~274s to ~228s.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "8843e3865d67ee9e0e3da37c040778dc4a1b40a8",
      "tree": "dc99cd8b9ca5fc8d7ccac945f80a2a44349ecc4e",
      "parents": [
        "a94f4c0c2f530abf3d0a349aa7dbc426016b4c72"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:43:36 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:43:36 2026 +0200"
      },
      "message": "cve_utils: load the not_reverts and not_fixes whitelists once per process\n\nEvery DyadState construction re-read scripts/not_reverts and\nscripts/not_fixes, including the find_vulns_dir() directory probing to\nlocate them.  update_dyad constructs one DyadState per CVE, so a full\nrun re-parsed the same two small files 18000 times each and spent a\nlarge pile of stat()/openat() calls doing it.\n\nThe files cannot change while a tool runs, so load each list once per\nprocess into a OnceLock and hand every DyadState a reference to the\nshared set.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "a94f4c0c2f530abf3d0a349aa7dbc426016b4c72",
      "tree": "43d9bfbaffe65b36c06a81a342201bfc1143a2e6",
      "parents": [
        "20b0499043b09b66ba9d321a05c02cdf59a24902"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:42:01 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:42:01 2026 +0200"
      },
      "message": "cve_utils: open the verhaal database read-only, memory-mapped and unserialized\n\nThe verhaal database is only ever read by the tools (verhaal generates\nit), but it was opened read-write, every page was fetched with pread()\nthrough sqlite\u0027s page cache, and sqlite\u0027s global memory-statistics\naccounting was left enabled.  That accounting takes a process-wide mutex\non every allocation inside sqlite, which serializes all worker threads\nwhen many per-thread connections hammer the database at once - a full\nupdate_dyad run spent most of its syscall time contending on futexes and\ndoing 100000+ pread() calls.\n\nOpen the database read-only, turn off the global memory statistics (once\nper process; nothing reads them), and let sqlite mmap the database file\nso page lookups go straight to the OS page cache.\n\nThis cuts the kernel time of a full update_dyad run over all 18000\npublished CVEs roughly in half (~21s to ~9s), and on a fast machine to\nless than a second as sqlite is _fast_ in read-only mode.\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    },
    {
      "commit": "20b0499043b09b66ba9d321a05c02cdf59a24902",
      "tree": "0aafdf7a582c4ad752c48cb9f0cae21121667aeb",
      "parents": [
        "eeae838aa5fb22da13f7cb53e299859cc7e0fa57"
      ],
      "author": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:40:30 2026 +0200"
      },
      "committer": {
        "name": "Greg Kroah-Hartman",
        "email": "gregkh@linuxfoundation.org",
        "time": "Sat Oct 24 16:40:30 2026 +0200"
      },
      "message": "cve_utils: skip the git lookup for full SHAs the database knows about\n\nKernel::from_id() always resolved the given id through libgit2 (revparse\nplus loading the commit object) before asking the verhaal database for\nthe release the commit was part of.  The ids that flow through here in\nbulk - every backport entry that found_in() and get_fixes() return, and\nevery id listed in a CVE\u0027s .sha1 file - are already full 40-character\nSHAs that come from (or are validated against) the verhaal database\nanyway, so the git round trip just burns time decompressing commit\nobjects.\n\nWhen the id is already a full sha1 and the database knows its release,\nuse it directly: it is in canonical form and the database only contains\ncommits that were part of a release.  Partial ids and ids the database\nhas not seen yet (e.g. commits not in any release) still go through the\ngit resolution path exactly as before.\n\nThis results in a measurable speedup, almost a full second as again,\nlibgit2 is slow...\n\nSigned-off-by: Greg Kroah-Hartman \u003cgregkh@linuxfoundation.org\u003e\n"
    }
  ],
  "next": "eeae838aa5fb22da13f7cb53e299859cc7e0fa57"
}
