[PATCH] KVM: PPC: Book3S HV: Hold KVM reference for HPT resize work

Myeonghun Pak <[email protected]>
Newsgroups gmane.linux.ports.ppc.embedded
Message-ID <CAGEsz8F60gmDxhvqiNib1h3=SoTMi_Uuz4PYOa8DrukHQaTK5w__7300.04641679756$1785278103$gmane$org@mail.gmail.com>
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: [email protected]
Signed-off-by: Myeonghun Pak <[email protected]>
---
 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
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.