RISC-V KVM can overwrite an existing G-stage table entry when
installing a huge leaf mapping. If the target huge range already has a
lower-level page table, kvm_riscv_gstage_set_pte() can replace the
non-leaf entry with a leaf PTE and disconnect the lower-level page
table.

Reject replacing a valid table entry with a leaf PTE. If huge-page
installation hits such a conflict, fall back to a 4K mapping for the
original faulting GPA in the MMU fault path, where the original GPA and
HFN are still available.

Suggested-by: Sashiko <[email protected]>
Fixes: 9d05c1fee837 ("RISC-V: KVM: Implement stage2 page table programming")
Signed-off-by: Jinyu Tang <[email protected]>
---
 arch/riscv/kvm/gstage.c |  6 ++++++
 arch/riscv/kvm/mmu.c    | 24 +++++++++++++++++++++++-
 2 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
index e5002cb9cbef..54d45addf18f 100644
--- a/arch/riscv/kvm/gstage.c
+++ b/arch/riscv/kvm/gstage.c
@@ -174,6 +174,12 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
 
        if (pte_val(*ptep) != pte_val(map->pte)) {
                bool was_invalid = !pte_val(*ptep);
+
+               /* Avoid replacing an existing lower-level table with a leaf 
mapping. */
+               if (!gstage_pte_leaf(ptep) && !was_invalid &&
+                   gstage_pte_leaf(&map->pte))
+                       return -EEXIST;
+
                set_pte(ptep, map->pte);
                if (gstage_pte_leaf(ptep) &&
                    !(was_invalid && 
riscv_has_extension_unlikely(RISCV_ISA_EXT_SVVPTC)))
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 6035b5ec9503..bfd6168ebe30 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -625,10 +625,11 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct 
kvm_memory_slot *memslot,
                      struct kvm_gstage_mapping *out_map)
 {
        int ret;
-       kvm_pfn_t hfn;
+       kvm_pfn_t fault_hfn, hfn;
        bool is_hugetlb;
        bool writable;
        unsigned int vma_pageshift;
+       gpa_t fault_gpa = gpa;
        gfn_t gfn = gpa >> PAGE_SHIFT;
        struct vm_area_struct *vma;
        struct kvm *kvm = vcpu->kvm;
@@ -709,6 +710,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct 
kvm_memory_slot *memslot,
        }
        if (is_error_noslot_pfn(hfn))
                return -EFAULT;
+       fault_hfn = hfn + ((fault_gpa >> PAGE_SHIFT) - gfn);
 
        /*
         * If logging is active then we allow writable pages only
@@ -734,9 +736,29 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct 
kvm_memory_slot *memslot,
                mark_page_dirty_in_slot(kvm, memslot, gfn);
                ret = kvm_riscv_gstage_map_page(&gstage, pcache, gpa, hfn << 
PAGE_SHIFT,
                                                vma_pagesize, false, true, 
out_map);
+               if (ret == -EEXIST) {
+                       /*
+                        * Retry at 4K granularity for the original faulting GPA
+                        * when a huge leaf cannot replace an existing table.
+                        */
+                       ret = kvm_riscv_gstage_map_page(&gstage, pcache, 
fault_gpa,
+                                                       fault_hfn << PAGE_SHIFT,
+                                                       PAGE_SIZE, false, true,
+                                                       out_map);
+               }
        } else {
                ret = kvm_riscv_gstage_map_page(&gstage, pcache, gpa, hfn << 
PAGE_SHIFT,
                                                vma_pagesize, true, true, 
out_map);
+               if (ret == -EEXIST) {
+                       /*
+                        * Retry at 4K granularity for the original faulting GPA
+                        * when a huge leaf cannot replace an existing table.
+                        */
+                       ret = kvm_riscv_gstage_map_page(&gstage, pcache, 
fault_gpa,
+                                                       fault_hfn << PAGE_SHIFT,
+                                                       PAGE_SIZE, true, true,
+                                                       out_map);
+               }
        }
 
        if (ret)
-- 
2.43.0


Reply via email to