KVM: arm64: Clear the VM's feature bitmap when kvm_setup_vcpu() fails
Fuad Tabba
reviewerPATCH v3 1/1 KVM: arm64: Clear the VM's feature bitmap when kvm_setup_vcpu() fails
__kvm_vcpu_set_target() copies the requested features into the VM-wide bitmap before kvm_setup_vcpu() runs and doesn't undo it when setup fails, so a rejected KVM_ARM_VCPU_INIT leaves the VM recording features that were never set up. With HAS_EL2 | HAS_EL2_E2H0 on a host without FEAT_NV1, kvm_vcpu_init_nested() returns -EINVAL before it allocates any nested stage-2 MMU, and vcpu_has_nv() is then true with nested_mmus_size == 0; its -ENOMEM paths do the same on a VM's first INIT.
Until one INIT has succeeded, every INIT that passes the feature check rewrites the bitmap and a failure leaves it there; once KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED is set, kvm_vcpu_init_changed() has already required the requested features to equal the bitmap, so the copy is a no-op. Clear the bitmap on failure while the flag is clear.
This showed up with the series that enables KVM_PRE_FAULT_MEMORY for arm64, since the generic kvm_vcpu_pre_fault_memory() calls vcpu_load() without checking that the vCPU has been initialised. After the rejected INIT, the first call's vcpu_load() reads hw_mmu still set to the canonical MMU and leaves it alone, but its vcpu_put() takes the vcpu_has_nv() branch into kvm_vcpu_put_hw_mmu(), which clears hw_mmu. The second call's vcpu_load() reads hw_mmu as NULL and takes the nested branch into get_s2_mmu_nested(), whose search over nested_mmus_size == 0 leaves s2_mmu NULL for the BUG_ON(atomic_read(&s2_mmu->refcnt)), under mmu_lock. On kvmarm/next with the series applied:
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000074 Call trace: kvm_vcpu_load_hw_mmu (arch/arm64/kvm/nested.c:891) (P) kvm_arch_vcpu_load (arch/arm64/kvm/arm.c:662) kvm_vcpu_pre_fault_memory (virt/kvm/kvm_main.c:170 virt/kvm/kvm_main.c:4349) kvm_vcpu_ioctl (virt/kvm/kvm_main.c:4639)
---
v3:
- Clear the bitmap on failure instead of saving and restoring it, only while KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED is clear, since with the flag set the copy was a no-op (Oliver).
- Commit message: drop the "latent, exposed by the series" framing and state the flag invariant behind the clear; the pre-fault crash is how the bug showed up.
- Dropped Lorenzo's Reviewed-by since the code changed.
v2:
- Commit message: say what the prefault series enables, bring the two-ioctl walk and the trace up from below the fold, the trace decoded, and drop the Fixes: on 1de10b7d13a97, which had nothing fallible after the copy (Lorenzo).
- Fold Lorenzo's Reviewed-by.
Reproduced on kvmarm/next plus the series under QEMU (-cpu max with an Apple M2 MIDR, which has_nv1() denies; kvm-arm.mode=nested):KVM_ARM_VCPU_INIT with HAS_EL2 | HAS_EL2_E2H0 returns -EINVAL, then KVM_PRE_FAULT_MEMORY twice. With the fix both calls return -ENOENT and the host is unaffected. Based on kvmarm/fixes; applies cleanly to v7.3-rc4 and to kvmarm/next.
v2: https://lore.kernel.org/r/[email protected]/
v1: https://lore.kernel.org/r/[email protected]/
arch/arm64/kvm/arm.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)
arch/arm64/kvm/arm.c +9 -1
@@ -1698,8 +1698,16 @@ static int __kvm_vcpu_set_target(struct kvm_vcpu *vcpu,bitmap_copy (kvm->arch.vcpu_features, &features,KVM_VCPU_MAX_FEATURES ); ret =kvm_setup_vcpu (vcpu); -if (ret) +if (ret) { +/* +* Clear the bitmap if setup fails on the first vCPU to be +* initialized. +*/ +if (!test_bit (KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED , &kvm->arch.flags)) +bitmap_zero (kvm->arch.vcpu_features,KVM_VCPU_MAX_FEATURES ); + goto out_unlock; + }/* Now we know what it is, we can reset it. */ kvm_reset_vcpu (vcpu);
base-commit: 6b1bca1b1ab77f60a62087337bfe6e2f0efb9e6d
Signature · 1 line
2.39.5
Quoted 16 lines · Fuad Tabba
__kvm_vcpu_set_target()copies the requested features into the VM-wide bitmap beforekvm_setup_vcpu()runs and doesn't undo it when setup fails, so a rejectedKVM_ARM_VCPU_INITleaves the VM recording features that were never set up. WithHAS_EL2|HAS_EL2_E2H0on a host withoutFEAT_NV1,kvm_vcpu_init_nested()returns-EINVALbefore it allocates any nested stage-2 MMU, andvcpu_has_nv()is then true withnested_mmus_size== 0; its-ENOMEMpaths do the same on a VM's first INIT.Until one INIT has succeeded, every INIT that passes the feature check rewrites the bitmap and a failure leaves it there; once
KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGUREDis set,kvm_vcpu_init_changed()has already required the requested features to equal the bitmap, so the copy is a no-op. Clear the bitmap on failure while the flag is clear.
Quoted 11 lines
This showed up with the series that enables
KVM_PRE_FAULT_MEMORYfor arm64, since the generickvm_vcpu_pre_fault_memory()callsvcpu_load()without checking that the vCPU has been initialised. After the rejected INIT, the first call'svcpu_load()readshw_mmustill set to the canonical MMU and leaves it alone, but itsvcpu_put()takes thevcpu_has_nv()branch intokvm_vcpu_put_hw_mmu(), which clearshw_mmu. The second call'svcpu_load()readshw_mmuas NULL and takes the nested branch intoget_s2_mmu_nested(), whose search overnested_mmus_size== 0 leavess2_mmuNULL for the BUG_ON(atomic_read(&s2_mmu->refcnt)), undermmu_lock. On kvmarm/next with the series applied:
This is no longer accurate at the latest revision of my series ([0])but maybe worth keeping for historic detail?
[0]: https://lore.kernel.org/kvmarm/[email protected]/
Quoted 8 lines
Unable to handle kernel NULL pointer dereference at virtual address 0000000000000074 Call trace: kvm_vcpu_load_hw_mmu (arch/arm64/kvm/nested.c:891) (P) kvm_arch_vcpu_load (arch/arm64/kvm/arm.c:662) kvm_vcpu_pre_fault_memory (virt/kvm/kvm_main.c:170 virt/kvm/kvm_main.c:4349) kvm_vcpu_ioctl (virt/kvm/kvm_main.c:4639)
Quoted 4 lines
Fixes 427733579744e ("KVM: arm64: Select default PMU in KVM_ARM_VCPU_INIT handler")Suggested-by Oliver Upton <[email protected]>Signed-off-by Fuad Tabba <[email protected]>
Seems reasonable to me so:
Quoted 53 lines
---
v3:
- Clear the bitmap on failure instead of saving and restoring it, only whileKVM_ARCH_FLAG_VCPU_FEATURES_CONFIGUREDis clear, since with the flag set the copy was a no-op (Oliver).
- Commit message: drop the "latent, exposed by the series" framing and state the flag invariant behind the clear; the pre-fault crash is how the bug showed up.
- Dropped Lorenzo's Reviewed-by since the code changed.v2:
- Commit message: say what the prefault series enables, bring the two-ioctl walk and the trace up from below the fold, the trace decoded, and drop the Fixes: on1de10b7d13a97, which had nothing fallible after the copy (Lorenzo).
- Fold Lorenzo's Reviewed-by.Reproduced on kvmarm/next plus the series under QEMU (-cpu max with an Apple M2 MIDR, which
has_nv1()denies; kvm-arm.mode=nested):KVM_ARM_VCPU_INITwithHAS_EL2|HAS_EL2_E2H0returns-EINVAL, thenKVM_PRE_FAULT_MEMORYtwice. With the fix both calls return-ENOENTand the host is unaffected. Based on kvmarm/fixes; applies cleanly to v7.3-rc4 and to kvmarm/next.v2: https://lore.kernel.org/r/[email protected]/
v1: https://lore.kernel.org/r/[email protected]/arch/arm64/kvm/arm.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-)arch/arm64/kvm/arm.c +9
-1@@ -1698,8 +1698,16 @@ static int __kvm_vcpu_set_target(struct kvm_vcpu *vcpu,bitmap_copy (kvm->arch.vcpu_features, &features,KVM_VCPU_MAX_FEATURES ); ret =kvm_setup_vcpu (vcpu); -if (ret) +if (ret) { +/* +* Clear the bitmap if setup fails on the first vCPU to be +* initialized. +*/ +if (!test_bit (KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED , &kvm->arch.flags)) +bitmap_zero (kvm->arch.vcpu_features,KVM_VCPU_MAX_FEATURES ); + goto out_unlock; + }/* Now we know what it is, we can reset it. */ kvm_reset_vcpu (vcpu);
Quoted 4 lines
base-commit:
6b1bca1b1ab77f60a62087337bfe6e2f0efb9e6d--
2.39.5
--
Cheers, Lorenzo