{
  "schema_version": "1.6.1",
  "id": "RPI-CVE-2024-26976",
  "modified": "2026-10-09T11:39:44Z",
  "upstream": [
    "CVE-2024-26976"
  ],
  "published": "2024-05-01T05:22:01Z",
  "summary": "KVM: Always flush async #PF workqueue when vCPU is being destroyed",
  "details": "In the Linux kernel, the following vulnerability has been resolved:\n\nKVM: Always flush async #PF workqueue when vCPU is being destroyed\n\nAlways flush the per-vCPU async #PF workqueue when a vCPU is clearing its\ncompletion queue, e.g. when a VM and all its vCPUs is being destroyed.\nKVM must ensure that none of its workqueue callbacks is running when the\nlast reference to the KVM _module_ is put.  Gifting a reference to the\nassociated VM prevents the workqueue callback from dereferencing freed\nvCPU/VM memory, but does not prevent the KVM module from being unloaded\nbefore the callback completes.\n\nDrop the misguided VM refcount gifting, as calling kvm_put_kvm() from\nasync_pf_execute() if kvm_put_kvm() flushes the async #PF workqueue will\nresult in deadlock.  async_pf_execute() can't return until kvm_put_kvm()\nfinishes, and kvm_put_kvm() can't return until async_pf_execute() finishes:\n\n WARNING: CPU: 8 PID: 251 at virt/kvm/kvm_main.c:1435 kvm_put_kvm+0x2d/0x320 [kvm]\n Modules linked in: vhost_net vhost vhost_iotlb tap kvm_intel kvm irqbypass\n CPU: 8 PID: 251 Comm: kworker/8:1 Tainted: G        W          6.6.0-rc1-e7af8d17224a-x86/gmem-vm #119\n Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015\n Workqueue: events async_pf_execute [kvm]\n RIP: 0010:kvm_put_kvm+0x2d/0x320 [kvm]\n Call Trace:\n  <TASK>\n  async_pf_execute+0x198/0x260 [kvm]\n  process_one_work+0x145/0x2d0\n  worker_thread+0x27e/0x3a0\n  kthread+0xba/0xe0\n  ret_from_fork+0x2d/0x50\n  ret_from_fork_asm+0x11/0x20\n  </TASK>\n ---[ end trace 0000000000000000 ]---\n INFO: task kworker/8:1:251 blocked for more than 120 seconds.\n       Tainted: G        W          6.6.0-rc1-e7af8d17224a-x86/gmem-vm #119\n \"echo 0 > /proc/sys/kernel/hung_task_timeout_secs\" disables this message.\n task:kworker/8:1     state:D stack:0     pid:251   ppid:2      flags:0x00004000\n Workqueue: events async_pf_execute [kvm]\n Call Trace:\n  <TASK>\n  __schedule+0x33f/0xa40\n  schedule+0x53/0xc0\n  schedule_timeout+0x12a/0x140\n  __wait_for_common+0x8d/0x1d0\n  __flush_work.isra.0+0x19f/0x2c0\n  kvm_clear_async_pf_completion_queue+0x129/0x190 [kvm]\n  kvm_arch_destroy_vm+0x78/0x1b0 [kvm]\n  kvm_put_kvm+0x1c1/0x320 [kvm]\n  async_pf_execute+0x198/0x260 [kvm]\n  process_one_work+0x145/0x2d0\n  worker_thread+0x27e/0x3a0\n  kthread+0xba/0xe0\n  ret_from_fork+0x2d/0x50\n  ret_from_fork_asm+0x11/0x20\n  </TASK>\n\nIf kvm_clear_async_pf_completion_queue() actually flushes the workqueue,\nthen there's no need to gift async_pf_execute() a reference because all\ninvocations of async_pf_execute() will be forced to complete before the\nvCPU and its VM are destroyed/freed.  And that in turn fixes the module\nunloading bug as __fput() won't do module_put() on the last vCPU reference\nuntil the vCPU has been freed, e.g. if closing the vCPU file also puts the\nlast reference to the KVM module.\n\nNote that kvm_check_async_pf_completion() may also take the work item off\nthe completion queue and so also needs to flush the work queue, as the\nwork will not be seen by kvm_clear_async_pf_completion_queue().  Waiting\non the workqueue could theoretically delay a vCPU due to waiting for the\nwork to complete, but that's a very, very small chance, and likely a very\nsmall delay.  kvm_arch_async_page_present_queued() unconditionally makes a\nnew request, i.e. will effectively delay entering the guest, so the\nremaining work is really just:\n\n        trace_kvm_async_pf_completed(addr, cr2_or_gpa);\n\n        __kvm_vcpu_wake_up(vcpu);\n\n        mmput(mm);\n\nand mmput() can't drop the last reference to the page tables if the vCPU is\nstill alive, i.e. the vCPU won't get stuck tearing down page tables.\n\nAdd a helper to do the flushing, specifically to deal with \"wakeup all\"\nwork items, as they aren't actually work items, i.e. are never placed in a\nworkqueue.  Trying to flush a bogus workqueue entry rightly makes\n__flush_work() complain (kudos to whoever added that sanity check).\n\nNote, commit 5f6de5cbebee (\"KVM: Prevent module exit until al\n---truncated---",
  "severity": [
    {
      "type": "CVSS_V3",
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
    }
  ],
  "affected": [
    {
      "package": {
        "ecosystem": "Raspberry Pi OS:12",
        "name": "linux"
      },
      "versions": [
        "1:6.1.47-1+rpt4"
      ],
      "ecosystem_specific": {
        "binaries": [
          {
            "binary_name": "linux-image-6.1.0-rpi3-rpi-2712",
            "binary_version": "1:6.1.47-1+rpt4"
          },
          {
            "binary_name": "linux-image-6.1.0-rpi3-rpi-v6",
            "binary_version": "1:6.1.47-1+rpt4"
          },
          {
            "binary_name": "linux-image-6.1.0-rpi3-rpi-v7",
            "binary_version": "1:6.1.47-1+rpt4"
          },
          {
            "binary_name": "linux-image-6.1.0-rpi3-rpi-v7l",
            "binary_version": "1:6.1.47-1+rpt4"
          },
          {
            "binary_name": "linux-image-6.1.0-rpi3-rpi-v8",
            "binary_version": "1:6.1.47-1+rpt4"
          }
        ]
      },
      "database_specific": {
        "per_version": {
          "1:6.1.47-1+rpt4": {
            "linux_commit": "655fc658a15ae7a6f37103754adb39ba52a9a14e",
            "upstream_version": "6.1.47",
            "assessment": "commit ancestry (strak rule)",
            "upstream_fixed": "6.1.84",
            "compiled_filter": false
          }
        }
      }
    }
  ],
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://www.cve.org/CVERecord?id=CVE-2024-26976"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/ab2c2f5d9576112ad22cfd3798071cb74693b1f5"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/82e25cc1c2e93c3023da98be282322fc08b61ffb"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/f8730d6335e5f43d09151fca1f0f41922209a264"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/83d3c5e309611ef593e2fcb78444fc8ceedf9bac"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/b54478d20375874aeee257744dedfd3e413432ff"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/a75afe480d4349c524d9c659b1a5a544dbc39a98"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/4f3a3bce428fb439c66a578adc447afce7b4a750"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/caa9af2e27c275e089d702cfbaaece3b42bca31b"
    },
    {
      "type": "FIX",
      "url": "https://git.kernel.org/stable/c/3d75b8aa5c29058a512db29da7cbee8052724157"
    }
  ],
  "database_specific": {
    "source": "https://git.kernel.org/pub/scm/linux/security/vulns.git",
    "upstream_modified": "2026-08-05T11:52:36Z",
    "severity": {
      "source": "kernel CNA",
      "cvss_v3_score": 7.8,
      "cvss_v3_severity": "High",
      "scenario": "AV:L - The vulnerability is reached exclusively through `/dev/kvm` ioctls (`KVM_CREATE_VM`/`KVM_CREATE_VCPU`/`KVM_SET_USER_MEMORY_REGION`/`KVM _RUN`) plus `close()` of the vCPU fd, all of which require an account on the host; there is no network, adjacent-network, or remote-peer data path into `virt/kvm/async_pf.c`.\nAC:L - The attacker controls both sides of the window \u2014 `madvise(MADV_PAGEOUT)` on its own guest memslot guarantees `__gfn_to_pfn_memslot()` returns async, queueing up to `ASYNC_PF_PER_VCPU` (64) work items per vCPU that each sleep in `get_user_pages_remote()` for the whole swap-in latency, while `close()` of the vCPU fd drives `__fput()`\u2192`module_put()` at a moment of the attacker's choosing. Nothing depends on a memory layout or system state the attacker cannot influence, and the flaw is unconditional on x86 since `CONFIG_KVM_ASYNC_PF` is `select`ed by `CONFIG_KVM`.\nPR:L - No capability check exists anywhere on the path \u2014 `kvm_dev_ioctl(KVM_CREATE_VM)`, `kvm_setup_async_pf()`, and vCPU teardown are all reachable by any user holding an open `/dev/kvm`, which distros grant to the ordinary `kvm` group and container/CI images commonly expose world-writable. The entire unsafe state that constitutes the bug \u2014 KVM module code still executing after the last `module_put()` \u2014 is created with zero privileges, so PR:L is selected as the higher-severity option over PR:H.\nUI:N - No victim action such as mounting a filesystem, opening a device, or plugging in hardware is required; `kvm_clear_async_pf_completion_queue()` and the premature `module_put()` run automatically from `__fput()`/`kvm_destroy_vm()` when the attacker's own process exits.\nS:U - The stale work callback and the freed module text/`async_pf_cache` slab are both host-kernel resources, so the flaw and its impact stay within a single security authority \u2014 this is a host-internal module-lifetime bug, not a guest-to-host or IOMMU boundary escape.\nC:H - This is a use-after-free in which `process_one_work()` dispatches `worker->current_func()` into a freed vmalloc module region and `async_pf_execute()` continues touching `struct kvm_async_pf` after `kmem_cache_destroy()` has released the slab; controlling the reallocated module-alloc/vmalloc contents yields kernel-mode execution and thus arbitrary kernel memory disclosure.\nI:H - The stale callback writes through the freed object (`list_add_tail(&apf->link, ...)`, `apf->vcpu = NULL`) into slab pages already returned to the allocator and reissued to other consumers, and executing a freed function pointer as kernel text is a direct control-flow hijack primitive \u2014 both are arbitrary-write class outcomes.\nA:H - Jumping into unmapped or repurposed vmalloc memory produces an immediate oops/panic, `kmem_cache_destroy()` on the still-populated `async_pf_cache` corrupts allocator state, and the same path drives full `kvm_destroy_vm()` teardown from a shared `system_wq` worker, which the commit's own trace shows wedging a kworker for more than 120 seconds."
    }
  }
}
