arch/arm64/include/asm/kvm_host.h | 1 + arch/arm64/include/asm/kvm_mmu.h | 1 + arch/arm64/kvm/mmu.c | 39 +++++- arch/arm64/kvm/nested.c | 4 +- arch/loongarch/kvm/mmu.c | 2 + arch/mips/kvm/mips.c | 2 + arch/mips/kvm/mmu.c | 2 + arch/riscv/kvm/mmu.c | 4 +- arch/riscv/kvm/vm.c | 2 + arch/x86/kvm/mmu/mmu.c | 4 +- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../testing/selftests/kvm/transfer_fd_test.c | 129 ++++++++++++++++++ virt/kvm/kvm_main.c | 3 + 13 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 tools/testing/selftests/kvm/transfer_fd_test.c
Hi Paolo,
syzbot running on Google production kernels ran into a double-free on
KVM/arm64 in kvm_mmu_free_memory_cache(). It turns out that loongarch
and mips also have a similar problem.
kvm_arch_flush_shadow_all() can be called on the same memslot
concurrently, leading to double-freeing in arm64 and mips. loongarch
is also affected: it can at least underflow some counters; I'm not sure
what else can break.
To get into this scenario, we need to have a process (P1) share an open
VM with another process (P2). If P1 closes its VM to leave P2 holding
the last reference, then there is a race between P1 exiting (exit_mm)
and P2 dropping its last reference to the VM.
exit_mm() and kvm_vm_release() both call kvm_mmu_notifier_release() on
the same KVM, and the only locks held are the KVM srcu lock and the MMU
notifier srcu lock.
Please see the arm64 patch for another description of the same race with
more context on the ensuing double-free in KVM/arm64.
The first three patches fix each broken architecture; each of those
patches have stable CCed with what I think are the appropriate Fixes.
After patching the locking for the broken architectures, it seems better
simply to have KVM take the MMU lock exclusively before calling
kvm_arch_flush_shadow_all() so that architectures don't need to worry
about it. Feel free to drop that patch, the fourth one, if you disagree
with it.
The fifth patch provides a repro (with a crude kernel patch to reliably
demonstrate the double-free). Please do not merge this.
The arm64 patch has been tested with the repro. The loongarch and mips
patches have been compile-tested only.
kvm_arch_guest_memory_reclaimed() is only implemented by one
architecture: x86. Its implementation does not need the KVM MMU lock to
be held.
This series is based on 7.1-rc2.
James Houghton (5):
KVM: arm64: Grab KVM MMU write lock in kvm_arch_flush_shadow_all()
KVM: loongarch: Grab MMU lock in kvm_arch_flush_shadow_all()
KVM: mips: Grab MMU lock in kvm_arch_flush_shadow_all()
KVM: Hold MMU lock exclusively when calling
kvm_arch_flush_shadow_all()
DO NOT MERGE: KVM: selftests: Reproducer for arm64 double-free
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/include/asm/kvm_mmu.h | 1 +
arch/arm64/kvm/mmu.c | 39 +++++-
arch/arm64/kvm/nested.c | 4 +-
arch/loongarch/kvm/mmu.c | 2 +
arch/mips/kvm/mips.c | 2 +
arch/mips/kvm/mmu.c | 2 +
arch/riscv/kvm/mmu.c | 4 +-
arch/riscv/kvm/vm.c | 2 +
arch/x86/kvm/mmu/mmu.c | 4 +-
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../testing/selftests/kvm/transfer_fd_test.c | 129 ++++++++++++++++++
virt/kvm/kvm_main.c | 3 +
13 files changed, 184 insertions(+), 10 deletions(-)
create mode 100644 tools/testing/selftests/kvm/transfer_fd_test.c
base-commit: 6d35786de28116ecf78797a62b84e6bf3c45aa5a
--
2.54.0.545.g6539524ca2-goog
On Mon, May 4, 2026 at 3:42 PM James Houghton <jthoughton@google.com> wrote:
>
> Hi Paolo,
>
> syzbot running on Google production kernels ran into a double-free on
> KVM/arm64 in kvm_mmu_free_memory_cache().
Here is the full splat:
==================================================================
BUG: KASAN: double-free in kvfree+0x30/0x40 mm/slub.c:7247
Free of addr ffff000810394000 by task syz.25.15225/156017
CPU: 2 UID: 0 PID: 156017 Comm: syz.25.15225 Kdump: loaded Not tainted
6.18.16-smp-DEV #1 NONE
Hardware name: Google
Call trace:
show_stack+0x2c/0x3c arch/arm64/kernel/stacktrace.c:499 (C)
__dump_stack+0x30/0x40 lib/dump_stack.c:94
dump_stack_lvl+0x198/0x264 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:402 [inline]
print_report+0xbc/0x254 mm/kasan/report.c:506
kasan_report_invalid_free+0x80/0xd0 mm/kasan/report.c:581
check_slab_allocation+0x134/0x1a0 mm/kasan/common.c:-1
__kasan_slab_pre_free+0x30/0x40 mm/kasan/common.c:261
kasan_slab_pre_free include/linux/kasan.h:199 [inline]
slab_free_hook mm/slub.c:2535 [inline]
slab_free mm/slub.c:6730 [inline]
kfree+0x140/0x548 mm/slub.c:6941
kvfree+0x30/0x40 mm/slub.c:7247
kvm_mmu_free_memory_cache+0x22c/0x2a0 virt/kvm/kvm_main.c:679
kvm_uninit_stage2_mmu+0x30/0x40 arch/arm64/kvm/mmu.c:1048
kvm_arch_flush_shadow_all+0x178/0x198 arch/arm64/kvm/nested.c:1113
kvm_flush_shadow_all virt/kvm/kvm_main.c:579 [inline]
kvm_mmu_notifier_release+0x48/0xac virt/kvm/kvm_main.c:1287
mn_hlist_release mm/mmu_notifier.c:321 [inline]
__mmu_notifier_release+0x29c/0x46c mm/mmu_notifier.c:359
mmu_notifier_release include/linux/mmu_notifier.h:406 [inline]
exit_mmap+0x100/0xb08 mm/mmap.c:1265
__mmput+0xb4/0x360 kernel/fork.c:1183
mmput+0x70/0xa8 kernel/fork.c:1206
exit_mm kernel/exit.c:616 [inline]
do_exit+0xc1c/0x26f4 kernel/exit.c:1025
do_group_exit+0x194/0x22c kernel/exit.c:1180
get_signal+0x1614/0x197c kernel/signal.c:3056
arch_do_signal_or_restart+0x25c/0x4a5c arch/arm64/kernel/signal.c:1637
exit_to_user_mode_loop+0x84/0x230 kernel/entry/common.c:44
exit_to_user_mode_prepare include/linux/irq-entry-common.h:228 [inline]
arm64_exit_to_user_mode arch/arm64/kernel/entry-common.c:103 [inline]
el0_svc+0x168/0x1dc arch/arm64/kernel/entry-common.c:750
el0t_64_sync_handler+0x68/0xdc arch/arm64/kernel/entry-common.c:768
el0t_64_sync+0x1b0/0x1b4 arch/arm64/kernel/entry.S:596
Allocated by task 156010:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x30/0x68 mm/kasan/common.c:78
kasan_save_alloc_info+0x40/0x50 mm/kasan/generic.c:573
poison_kmalloc_redzone mm/kasan/common.c:401 [inline]
__kasan_kmalloc+0x9c/0xb4 mm/kasan/common.c:418
kasan_kmalloc include/linux/kasan.h:263 [inline]
__do_kmalloc_node mm/slub.c:5712 [inline]
__kvmalloc_node_noprof+0x4e0/0x7d0 mm/slub.c:7204
kvmalloc_array_node_noprof include/linux/slab.h:1140 [inline]
__kvm_mmu_topup_memory_cache+0x460/0x5b8 virt/kvm/kvm_main.c:638
kvm_mmu_split_huge_pages+0x1d8/0x338 arch/arm64/kvm/mmu.c:143
kvm_mmu_split_memory_region arch/arm64/kvm/mmu.c:1306 [inline]
kvm_arch_commit_memory_region+0x648/0x814 arch/arm64/kvm/mmu.c:2432
kvm_commit_memory_region virt/kvm/kvm_main.c:2244 [inline]
kvm_set_memslot+0xbec/0x120c virt/kvm/kvm_main.c:2506
kvm_set_memory_region+0x7f4/0x9ec virt/kvm/kvm_main.c:2643
kvm_vm_ioctl_set_memory_region+0x74/0xdc virt/kvm/kvm_main.c:2677
kvm_vm_ioctl+0xc28/0xe50 virt/kvm/kvm_main.c:6368
vfs_ioctl fs/ioctl.c:52 [inline]
__do_sys_ioctl fs/ioctl.c:598 [inline]
__se_sys_ioctl fs/ioctl.c:584 [inline]
__arm64_sys_ioctl+0x14c/0x1c4 fs/ioctl.c:584
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0xa4/0x25c arch/arm64/kernel/syscall.c:49
el0_svc_common+0x13c/0x250 arch/arm64/kernel/syscall.c:132
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:151
el0_svc+0x5c/0x1dc arch/arm64/kernel/entry-common.c:749
el0t_64_sync_handler+0x68/0xdc arch/arm64/kernel/entry-common.c:768
el0t_64_sync+0x1b0/0x1b4 arch/arm64/kernel/entry.S:596
Freed by task 156010:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x30/0x68 mm/kasan/common.c:78
__kasan_save_free_info+0x54/0x6c mm/kasan/generic.c:587
kasan_save_free_info mm/kasan/kasan.h:406 [inline]
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x74/0xa4 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2590 [inline]
slab_free mm/slub.c:6730 [inline]
kfree+0x17c/0x548 mm/slub.c:6941
kvfree+0x30/0x40 mm/slub.c:7247
kvm_mmu_free_memory_cache+0x22c/0x2a0 virt/kvm/kvm_main.c:679
kvm_uninit_stage2_mmu+0x30/0x40 arch/arm64/kvm/mmu.c:1048
kvm_arch_flush_shadow_all+0x178/0x198 arch/arm64/kvm/nested.c:1113
kvm_flush_shadow_all virt/kvm/kvm_main.c:579 [inline]
kvm_mmu_notifier_release+0x48/0xac virt/kvm/kvm_main.c:1287
mmu_notifier_unregister+0xf0/0x330 mm/mmu_notifier.c:815
kvm_destroy_vm virt/kvm/kvm_main.c:1767 [inline]
kvm_put_kvm+0x664/0xaf0 virt/kvm/kvm_main.c:1825
kvm_vm_release+0x4c/0x60 virt/kvm/kvm_main.c:1848
__fput+0x340/0x754 fs/file_table.c:495
____fput+0x20/0x58 fs/file_table.c:523
task_work_run+0x1d8/0x268 kernel/task_work.c:233
exit_task_work include/linux/task_work.h:40 [inline]
do_exit+0xcc0/0x26f4 kernel/exit.c:1037
do_group_exit+0x194/0x22c kernel/exit.c:1180
get_signal+0x1614/0x197c kernel/signal.c:3056
arch_do_signal_or_restart+0x25c/0x4a5c arch/arm64/kernel/signal.c:1637
exit_to_user_mode_loop+0x84/0x230 kernel/entry/common.c:44
exit_to_user_mode_prepare include/linux/irq-entry-common.h:228 [inline]
arm64_exit_to_user_mode arch/arm64/kernel/entry-common.c:103 [inline]
el0_svc+0x168/0x1dc arch/arm64/kernel/entry-common.c:750
el0t_64_sync_handler+0x68/0xdc arch/arm64/kernel/entry-common.c:768
el0t_64_sync+0x1b0/0x1b4 arch/arm64/kernel/entry.S:596
The buggy address belongs to the object at ffff000810394000
which belongs to the cache kmalloc-cg-8k of size 8192
The buggy address is located 0 bytes inside of
8192-byte region [ffff000810394000, ffff000810396000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x810390
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
memcg:ffff0008ceb3d481
anon flags: 0x100000000000040(head|node=0|zone=1)
page_type: f5(slab)
raw: 0100000000000040 ffff00001000b200 0000000000000000 0000000000000001
raw: 0000000000000000 0000000080020002 00000000f5000000 ffff0008ceb3d481
head: 0100000000000040 ffff00001000b200 0000000000000000 0000000000000001
head: 0000000000000000 0000000080020002 00000000f5000000 ffff0008ceb3d481
head: 0100000000000003 fffffdffe040e401 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask
0x1528c0(GFP_NOWAIT|__GFP_SENSITIVE|__GFP_IO|__GFP_FS|__GFP_NORETRY|__GFP_COMP|__GFP_HARDWALL),
pid 18319, tgid 17452 (ContainerMgr), ts 882149108930, free_ts
882148954562
set_page_owner include/linux/page_owner.h:32 [inline]
__post_alloc_hook+0x268/0x288 mm/page_alloc.c:1962
prep_new_page mm/page_alloc.c:1976 [inline]
get_page_from_freelist+0x2dc0/0x2f1c mm/page_alloc.c:4089
__alloc_frozen_pages_noprof+0x254/0x120c mm/page_alloc.c:6001
alloc_pages_mpol+0x1e4/0x444 mm/mempolicy.c:2428
alloc_frozen_pages_noprof+0xe0/0x210 mm/mempolicy.c:2499
alloc_slab_page mm/slub.c:3106 [inline]
allocate_slab+0xc0/0x48c mm/slub.c:3280
new_slab mm/slub.c:3334 [inline]
___slab_alloc+0xc4c/0x13d4 mm/slub.c:4717
__slab_alloc+0x3c/0x6c mm/slub.c:4840
__slab_alloc_node mm/slub.c:4916 [inline]
slab_alloc_node mm/slub.c:5338 [inline]
__do_kmalloc_node mm/slub.c:5711 [inline]
__kvmalloc_node_noprof+0x5dc/0x7d0 mm/slub.c:7204
seq_buf_alloc fs/seq_file.c:38 [inline]
seq_read_iter+0x510/0xd08 fs/seq_file.c:254
proc_reg_read_iter+0x174/0x2a0 fs/proc/inode.c:299
new_sync_read fs/read_write.c:491 [inline]
vfs_read+0x580/0xac8 fs/read_write.c:572
ksys_read+0x128/0x220 fs/read_write.c:715
__do_sys_read fs/read_write.c:724 [inline]
__se_sys_read fs/read_write.c:722 [inline]
__arm64_sys_read+0x7c/0x90 fs/read_write.c:722
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0xa4/0x25c arch/arm64/kernel/syscall.c:49
el0_svc_common+0x13c/0x250 arch/arm64/kernel/syscall.c:132
page last free pid 18319 tgid 17452 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1483 [inline]
__free_frozen_pages+0x1274/0x1798 mm/page_alloc.c:3064
free_frozen_pages+0x14/0x20 mm/page_alloc.c:3114
free_large_kmalloc+0xc0/0x154 mm/slub.c:6867
kfree+0x338/0x548 mm/slub.c:6935
kvfree+0x30/0x40 mm/slub.c:7247
seq_release+0x58/0x7c fs/seq_file.c:368
proc_seq_release+0x9c/0xbc fs/proc/generic.c:623
close_pdeo+0x1b0/0x340 fs/proc/inode.c:242
proc_reg_release+0x144/0x174 fs/proc/inode.c:547
__fput+0x340/0x754 fs/file_table.c:495
fput_close_sync+0x110/0x2c0 fs/file_table.c:600
__do_sys_close fs/open.c:1591 [inline]
__se_sys_close fs/open.c:1576 [inline]
__arm64_sys_close+0x7c/0x118 fs/open.c:1576
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0xa4/0x25c arch/arm64/kernel/syscall.c:49
el0_svc_common+0x13c/0x250 arch/arm64/kernel/syscall.c:132
do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:151
el0_svc+0x5c/0x1dc arch/arm64/kernel/entry-common.c:749
Memory state around the buggy address:
ffff000810393f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff000810393f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff000810394000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff000810394080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff000810394100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
© 2016 - 2026 Red Hat, Inc.