| From e86fc1a3a3e9b4850fe74d738e3cfcf4297d8bba Mon Sep 17 00:00:00 2001 |
| From: Marc Zyngier <maz@kernel.org> |
| Date: Thu, 16 Mar 2023 17:45:45 +0000 |
| Subject: KVM: arm64: Disable interrupts while walking userspace PTs |
| |
| From: Marc Zyngier <maz@kernel.org> |
| |
| commit e86fc1a3a3e9b4850fe74d738e3cfcf4297d8bba upstream. |
| |
| We walk the userspace PTs to discover what mapping size was |
| used there. However, this can race against the userspace tables |
| being freed, and we end-up in the weeds. |
| |
| Thankfully, the mm code is being generous and will IPI us when |
| doing so. So let's implement our part of the bargain and disable |
| interrupts around the walk. This ensures that nothing terrible |
| happens during that time. |
| |
| We still need to handle the removal of the page tables before |
| the walk. For that, allow get_user_mapping_size() to return an |
| error, and make sure this error can be propagated all the way |
| to the the exit handler. |
| |
| Signed-off-by: Marc Zyngier <maz@kernel.org> |
| Cc: stable@vger.kernel.org |
| Link: https://lore.kernel.org/r/20230316174546.3777507-2-maz@kernel.org |
| Signed-off-by: Oliver Upton <oliver.upton@linux.dev> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| arch/arm64/kvm/mmu.c | 45 ++++++++++++++++++++++++++++++++++++++------- |
| 1 file changed, 38 insertions(+), 7 deletions(-) |
| |
| --- a/arch/arm64/kvm/mmu.c |
| +++ b/arch/arm64/kvm/mmu.c |
| @@ -646,14 +646,33 @@ static int get_user_mapping_size(struct |
| CONFIG_PGTABLE_LEVELS), |
| .mm_ops = &kvm_user_mm_ops, |
| }; |
| + unsigned long flags; |
| kvm_pte_t pte = 0; /* Keep GCC quiet... */ |
| u32 level = ~0; |
| int ret; |
| |
| + /* |
| + * Disable IRQs so that we hazard against a concurrent |
| + * teardown of the userspace page tables (which relies on |
| + * IPI-ing threads). |
| + */ |
| + local_irq_save(flags); |
| ret = kvm_pgtable_get_leaf(&pgt, addr, &pte, &level); |
| - VM_BUG_ON(ret); |
| - VM_BUG_ON(level >= KVM_PGTABLE_MAX_LEVELS); |
| - VM_BUG_ON(!(pte & PTE_VALID)); |
| + local_irq_restore(flags); |
| + |
| + if (ret) |
| + return ret; |
| + |
| + /* |
| + * Not seeing an error, but not updating level? Something went |
| + * deeply wrong... |
| + */ |
| + if (WARN_ON(level >= KVM_PGTABLE_MAX_LEVELS)) |
| + return -EFAULT; |
| + |
| + /* Oops, the userspace PTs are gone... Replay the fault */ |
| + if (!kvm_pte_valid(pte)) |
| + return -EAGAIN; |
| |
| return BIT(ARM64_HW_PGTABLE_LEVEL_SHIFT(level)); |
| } |
| @@ -1006,7 +1025,7 @@ static bool fault_supports_stage2_huge_m |
| * |
| * Returns the size of the mapping. |
| */ |
| -static unsigned long |
| +static long |
| transparent_hugepage_adjust(struct kvm *kvm, struct kvm_memory_slot *memslot, |
| unsigned long hva, kvm_pfn_t *pfnp, |
| phys_addr_t *ipap) |
| @@ -1018,8 +1037,15 @@ transparent_hugepage_adjust(struct kvm * |
| * sure that the HVA and IPA are sufficiently aligned and that the |
| * block map is contained within the memslot. |
| */ |
| - if (fault_supports_stage2_huge_mapping(memslot, hva, PMD_SIZE) && |
| - get_user_mapping_size(kvm, hva) >= PMD_SIZE) { |
| + if (fault_supports_stage2_huge_mapping(memslot, hva, PMD_SIZE)) { |
| + int sz = get_user_mapping_size(kvm, hva); |
| + |
| + if (sz < 0) |
| + return sz; |
| + |
| + if (sz < PMD_SIZE) |
| + return PAGE_SIZE; |
| + |
| /* |
| * The address we faulted on is backed by a transparent huge |
| * page. However, because we map the compound huge page and |
| @@ -1138,7 +1164,7 @@ static int user_mem_abort(struct kvm_vcp |
| bool logging_active = memslot_is_logging(memslot); |
| bool use_read_lock = false; |
| unsigned long fault_level = kvm_vcpu_trap_get_fault_level(vcpu); |
| - unsigned long vma_pagesize, fault_granule; |
| + long vma_pagesize, fault_granule; |
| enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_R; |
| struct kvm_pgtable *pgt; |
| |
| @@ -1295,6 +1321,11 @@ static int user_mem_abort(struct kvm_vcp |
| vma_pagesize = transparent_hugepage_adjust(kvm, memslot, |
| hva, &pfn, |
| &fault_ipa); |
| + |
| + if (vma_pagesize < 0) { |
| + ret = vma_pagesize; |
| + goto out_unlock; |
| + } |
| } |
| |
| if (fault_status != FSC_PERM && !device && kvm_has_mte(kvm)) { |