Re: [PATCH v2] KVM: arm64: nv: Keep the shadow S2 MMUs at fixed addresses
Wei-Lin Chang <[email protected]> Thu, 6 Aug 2026 14:38:11 +0100
| Newsgroups | dev.linux.lists.kvmarm,dev.linux.lists.sashiko-reviews |
|---|---|
| Message-ID | <ump35q2osiduwshuz2qoawpeirjwawch3prm4odto4mgzr37sg@3x7jvpivobji> |
On Thu, Aug 06, 2026 at 06:38:59AM +0000, [email protected] wrote: > Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: > > New issues: > - [Critical] Deterministic NULL pointer dereference and Use-After-Free race on the `nested_mmus` array after `kvm_arch_flush_shadow_all` executes. > > Pre-existing issues: > - [Critical] Deterministic NULL pointer dereference in `get_s2_mmu_nested` when `nested_mmus_size` is 0. > -- > > --- Patch [1]: [PATCH v2] KVM: arm64: nv: Keep the shadow S2 MMUs at fixed addresses --- > commit 70c0982ec06bd8ebb390c1b7ff665dae83887bb8 > Author: Karl Mehltretter <[email protected]> > KVM: arm64: nv: Keep the shadow S2 MMUs at fixed addresses > > This patch turns the nested_mmus array into a fixed-size pointer table > allocated once during VM creation. The S2 MMUs are allocated individually > during vCPU initialization to keep their addresses stable, fixing a > slab-use-after-free issue. > > v1: https://lore.kernel.org/r/[email protected]/ > > > diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c > > --- a/arch/arm64/kvm/nested.c > > +++ b/arch/arm64/kvm/nested.c > > @@ -90,44 +104,44 @@ int kvm_vcpu_init_nested(struct kvm_vcpu *vcpu) > > [ ... ] > > > for (i = 0; i < S2_MMU_PER_VCPU; i++) { > > mmu = kzalloc_obj(*mmu, GFP_KERNEL_ACCOUNT); > > if (!mmu) { > > ret = -ENOMEM; > > goto err_free_mmus; > > } > > > > ret = init_nested_s2_mmu(kvm, mmu); > > if (ret) { > > kfree(mmu); > > goto err_free_vncr; > > } > > > > kvm->arch.nested_mmus[kvm->arch.nested_mmus_size + i] = mmu; > > [Severity: Critical] > Can this lead to a NULL pointer dereference or use-after-free? > > The patch shifts the allocation of kvm->arch.nested_mmus to VM creation in > kvm_init_nested(), but kvm_arch_flush_shadow_all() still frees the array and > sets the pointer to NULL and size to 0. > > If an unprivileged user process triggers kvm_mmu_notifier_release(), for > example by passing the file descriptor to a child process and exiting, > kvm_arch_flush_shadow_all() will free the array. > > A subsequent KVM_ARM_VCPU_INIT ioctl will bypass the size check in > kvm_vcpu_init_nested() because num_mmus is greater than 0 and > nested_mmus_size is 0. It will allocate an MMU and write it directly to > kvm->arch.nested_mmus[kvm->arch.nested_mmus_size + i], which dereferences > the now NULL pointer. > > Additionally, since this write happens outside mmu_lock, it could race with > a concurrent kvm_arch_flush_shadow_all() execution and write into freed > memory. I don't think so. KVM_ARM_VCPU_INIT can not be run after kvm_arch_flush_shadow_all() have started. kvm_arch_flush_shadow_all() starts after the last use count of mm_struct is gone. Additionally vCPU/VM ioctls require current->mm == kvm->mm. A non CLONE_VM child will fail, while a CLONE_VM child keeps the mm_struct alive and kvm_arch_flush_shadow_all() can not run. > > [ ... ] > > > @@ -805,7 +819,7 @@ static struct kvm_s2_mmu *get_s2_mmu_nested(struct kvm_vcpu *vcpu) > > for (i = kvm->arch.nested_mmus_next; > > i < (kvm->arch.nested_mmus_size + kvm->arch.nested_mmus_next); > > i++) { > > - s2_mmu = &kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; > > + s2_mmu = kvm->arch.nested_mmus[i % kvm->arch.nested_mmus_size]; > > > > if (atomic_read(&s2_mmu->refcnt) == 0) > > break; > > } > > [Severity: Critical] > This is a pre-existing issue, but can this lead to a NULL pointer > dereference when nested_mmus_size is 0? > > If kvm_arch_flush_shadow_all() executes while a vCPU is active, it sets > kvm->arch.nested_mmus_size to 0. > > When the vCPU subsequently enters get_s2_mmu_nested(), lookup_s2_mmu() > evaluates the empty array and returns NULL, assigning NULL to s2_mmu. > The for loop condition i < (0 + next) evaluates to false, causing the loop > to be completely bypassed. > > The function then executes BUG_ON(atomic_read(&s2_mmu->refcnt)) just below > this loop, which dereferences the uninitialized or NULL s2_mmu pointer. > This can be reliably triggered by terminating a VM process while it is > executing nested virtualization workloads. Same as the above. Thanks, Wei-Lin Chang > > -- > Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1