[PATCH] KVM: PPC: Book3S HV: Hold KVM reference for HPT resize work
From: Myeonghun Pak
Date: Tue Jul 28 2026 - 15:09:33 EST
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@xxxxxxxxxxxxxxx
Signed-off-by: Myeonghun Pak <mhun512@xxxxxxxxx>
---
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.h
b/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.c
b/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(struct
work_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(struct
work_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