[PATCH] KVM: PPC: Book3S HV: Hold KVM reference for HPT resize work
From: Myeonghun Pak <hidden>
Date: 2026-07-28 19:04:31
Also in:
kvm, lkml
Subsystem:
kernel virtual machine for powerpc (kvm/powerpc), linux for powerpc (32-bit and 64-bit), the rest · Maintainers:
Madhavan Srinivasan, Linus Torvalds
KVM_PPC_RESIZE_HPT_PREPARE schedules resize_hpt_prepare_work() with a
raw pointer to the enclosing KVM object. Book3S-HV VM destruction does
not wait for this work, so the worker can access struct kvm after
kvm_destroy_vm() has freed it.
A later prepare request with a different shift can also replace
kvm->arch.resize_hpt while the previous work remains busy. Such detached
work still retains its raw KVM pointer.
A pseries hash guest can initiate the work with H_RESIZE_HPT_PREPARE.
QEMU handles KVM_EXIT_PAPR_HCALL by issuing the prepare ioctl, and the
guest can then request RTAS power-off to initiate VM teardown. For a
4 GiB guest, QEMU permits shift=26, which makes the unlocked allocation
interval long enough to reproduce the race without instrumentation.
On unmodified upstream master 62cc90241548, the first shift=26
iteration produced:
BUG: KASAN: slab-use-after-free in mutex_lock+0x44/0xf0
Write of size 8 at addr c000000006330c18 by task kworker/0:1/10
Workqueue: events resize_hpt_prepare_work
mutex_lock
resize_hpt_prepare_work
process_one_work
worker_thread
The KVM object was freed concurrently by:
kvm_destroy_vm
kvm_vm_release
__fput
sys_close
Hold a KVM reference for every scheduled resize work and release it only
after the callback has stopped accessing the KVM. This covers both the
current resize and busy work detached by a replacement request. Once all
workers have dropped their references, VM destruction can safely release
the completed current resize object.
With this patch, KASAN reported no error in 10 shift=26-to-18 replacement
iterations, 1,000 shift=18-to-19 replacement iterations, or a
deterministic run that paused both callbacks for one second across VM
close. A powerpc64le vmlinux build, git diff --check, and strict
checkpatch also passed.
Fixes: 5e9859699aba ("KVM: PPC: Book3S HV: Outline of KVM-HV HPT
resizing implementation")
Cc: stable@vger.kernel.org
Signed-off-by: Myeonghun Pak <redacted>
---
arch/powerpc/include/asm/kvm_ppc.h | 1 +
arch/powerpc/kvm/book3s_64_mmu_hv.c | 18 +++++++++++++++++-
arch/powerpc/kvm/book3s_hv.c | 2 ++
3 files changed, 20 insertions(+), 1 deletion(-)
AI disclosure: I used AI assistance during the audit and am treating the
issue as public as required by Documentation/process/security-bugs.rst.
The reproducer and full KASAN trace have not been posted publicly and
are available on request.
diff --git a/arch/powerpc/include/asm/kvm_ppc.hb/arch/powerpc/include/asm/kvm_ppc.h index 0953f2daa4660..371268a794c3c 100644
--- a/arch/powerpc/include/asm/kvm_ppc.h
+++ b/arch/powerpc/include/asm/kvm_ppc.h@@ -226,6 +226,7 @@ extern int kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm, struct kvm_ppc_resize_hpt *rhpt); extern int kvm_vm_ioctl_resize_hpt_commit(struct kvm *kvm, struct kvm_ppc_resize_hpt *rhpt); +void kvmppc_release_hpt_resize(struct kvm *kvm); int kvm_vcpu_ioctl_interrupt(struct kvm_vcpu *vcpu, struct kvm_interrupt *irq);
diff --git a/arch/powerpc/kvm/book3s_64_mmu_hv.cb/arch/powerpc/kvm/book3s_64_mmu_hv.c index 2ccb3d138f46c..7776d6bcb5c53 100644
--- a/arch/powerpc/kvm/book3s_64_mmu_hv.c
+++ b/arch/powerpc/kvm/book3s_64_mmu_hv.c@@ -1409,6 +1409,17 @@ static void resize_hpt_release(struct kvm *kvm,struct kvm_resize_hpt *resize)
kvm->arch.resize_hpt = NULL;
}
+void kvmppc_release_hpt_resize(struct kvm *kvm)
+{
+ /*
+ * Each resize worker holds a KVM reference, so all workers have stopped
+ * accessing the KVM before VM teardown reaches this point.
+ */
+ mutex_lock(&kvm->arch.mmu_setup_lock);
+ resize_hpt_release(kvm, kvm->arch.resize_hpt);
+ mutex_unlock(&kvm->arch.mmu_setup_lock);
+}
+
static void resize_hpt_prepare_work(struct work_struct *work)
{
struct kvm_resize_hpt *resize = container_of(work,@@ -1417,8 +1428,10 @@ static void resize_hpt_prepare_work(structwork_struct *work)
struct kvm *kvm = resize->kvm;
int err = 0;
- if (WARN_ON(resize->error != -EBUSY))
+ if (WARN_ON(resize->error != -EBUSY)) {
+ kvm_put_kvm(kvm);
return;
+ }
mutex_lock(&kvm->arch.mmu_setup_lock);
@@ -1452,6 +1465,7 @@ static void resize_hpt_prepare_work(structwork_struct *work)
resize_hpt_release(kvm, resize);
mutex_unlock(&kvm->arch.mmu_setup_lock);
+ kvm_put_kvm(kvm);
}
int kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm,@@ -1506,6 +1520,8 @@ int kvm_vm_ioctl_resize_hpt_prepare(struct kvm *kvm, INIT_WORK(&resize->work, resize_hpt_prepare_work); kvm->arch.resize_hpt = resize; + /* Keep the KVM alive until the worker has finished accessing it. */ + kvm_get_kvm(kvm); schedule_work(&resize->work); ret = 100; /* estimated time in ms */
diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
index 61dbeea317f3b..3425aa8b6a8ec 100644
--- a/arch/powerpc/kvm/book3s_hv.c
+++ b/arch/powerpc/kvm/book3s_hv.c@@ -5889,6 +5889,8 @@ static void kvmppc_core_destroy_vm_hv(struct kvm *kvm) if (!cpu_has_feature(CPU_FTR_ARCH_300)) kvm_hv_vm_deactivated(); + kvmppc_release_hpt_resize(kvm); + kvmppc_free_vcores(kvm); --
2.47.2