• Gavin Shan's avatar
    KVM: Avoid illegal stage2 mapping on invalid memory slot · 2230f9e1
    Gavin Shan authored
    We run into guest hang in edk2 firmware when KSM is kept as running on
    the host. The edk2 firmware is waiting for status 0x80 from QEMU's pflash
    device (TYPE_PFLASH_CFI01) during the operation of sector erasing or
    buffered write. The status is returned by reading the memory region of
    the pflash device and the read request should have been forwarded to QEMU
    and emulated by it. Unfortunately, the read request is covered by an
    illegal stage2 mapping when the guest hang issue occurs. The read request
    is completed with QEMU bypassed and wrong status is fetched. The edk2
    firmware runs into an infinite loop with the wrong status.
    
    The illegal stage2 mapping is populated due to same page sharing by KSM
    at (C) even the associated memory slot has been marked as invalid at (B)
    when the memory slot is requested to be deleted. It's notable that the
    active and inactive memory slots can't be swapped when we're in the middle
    of kvm_mmu_notifier_change_pte() because kvm->mn_active_invalidate_count
    is elevated, and kvm_swap_active_memslots() will busy loop until it reaches
    to zero again. Besides, the swapping from the active to the inactive memory
    slots is also avoided by holding &kvm->srcu in __kvm_handle_hva_range(),
    corresponding to synchronize_srcu_expedited() in kvm_swap_active_memslots().
    
      CPU-A                    CPU-B
      -----                    -----
                               ioctl(kvm_fd, KVM_SET_USER_MEMORY_REGION)
                               kvm_vm_ioctl_set_memory_region
                               kvm_set_memory_region
                               __kvm_set_memory_region
                               kvm_set_memslot(kvm, old, NULL, KVM_MR_DELETE)
                                 kvm_invalidate_memslot
                                   kvm_copy_memslot
                                   kvm_replace_memslot
                                   kvm_swap_active_memslots        (A)
                                   kvm_arch_flush_shadow_memslot   (B)
      same page sharing by KSM
      kvm_mmu_notifier_invalidate_range_start
            :
      kvm_mmu_notifier_change_pte
        kvm_handle_hva_range
        __kvm_handle_hva_range
        kvm_set_spte_gfn            (C)
            :
      kvm_mmu_notifier_invalidate_range_end
    
    Fix the issue by skipping the invalid memory slot at (C) to avoid the
    illegal stage2 mapping so that the read request for the pflash's status
    is forwarded to QEMU and emulated by it. In this way, the correct pflash's
    status can be returned from QEMU to break the infinite loop in the edk2
    firmware.
    
    We tried a git-bisect and the first problematic commit is cd4c7183 ("
    KVM: arm64: Convert to the gfn-based MMU notifier callbacks"). With this,
    clean_dcache_guest_page() is called after the memory slots are iterated
    in kvm_mmu_notifier_change_pte(). clean_dcache_guest_page() is called
    before the iteration on the memory slots before this commit. This change
    literally enlarges the racy window between kvm_mmu_notifier_change_pte()
    and memory slot removal so that we're able to reproduce the issue in a
    practical test case. However, the issue exists since commit d5d8184d
    ("KVM: ARM: Memory virtualization setup").
    
    Cc: stable@vger.kernel.org # v3.9+
    Fixes: d5d8184d ("KVM: ARM: Memory virtualization setup")
    Reported-by: default avatarShuai Hu <hshuai@redhat.com>
    Reported-by: default avatarZhenyu Zhang <zhenyzha@redhat.com>
    Signed-off-by: default avatarGavin Shan <gshan@redhat.com>
    Reviewed-by: default avatarDavid Hildenbrand <david@redhat.com>
    Reviewed-by: default avatarOliver Upton <oliver.upton@linux.dev>
    Reviewed-by: default avatarPeter Xu <peterx@redhat.com>
    Reviewed-by: default avatarSean Christopherson <seanjc@google.com>
    Reviewed-by: default avatarShaoqin Huang <shahuang@redhat.com>
    Message-Id: <20230615054259.14911-1-gshan@redhat.com>
    Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
    2230f9e1
kvm_main.c 155 KB