[PATCH] KVM: riscv: Avoid overwriting existing G-stage tables with huge leaves

Jinyu Tang <[email protected]>
Newsgroups org.infradead.lists.kvm-riscv,org.infradead.lists.linux-riscv,org.kernel.vger.kvm,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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. This disconnects the lower-level page table and can
change guest-visible mappings or leak the page-table page.

Reject replacing a valid non-leaf PTE with a leaf PTE. If huge-page
installation hits such a conflict, fall back to a 4K mapping for the
faulting GPA and leave the existing page-table structure intact.

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 | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
index e5002cb9cbef..23cb35e36073 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)))
@@ -211,12 +217,13 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
 			      struct kvm_gstage_mapping *out_map)
 {
 	bool found_leaf;
+	phys_addr_t huge_page_offset;
 	u32 ptep_level;
 	pgprot_t prot;
 	pte_t *ptep;
 	int ret;
 
-	out_map->addr = gpa;
+	out_map->addr = gpa & PAGE_MASK;
 	out_map->level = 0;
 
 	ret = gstage_page_size_to_level(gstage, page_size, &out_map->level);
@@ -287,7 +294,18 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
 	out_map->pte = pfn_pte(PFN_DOWN(hpa), prot);
 	out_map->pte = pte_mkdirty(out_map->pte);
 
-	return kvm_riscv_gstage_set_pte(gstage, pcache, out_map);
+	ret = kvm_riscv_gstage_set_pte(gstage, pcache, out_map);
+	if (ret == -EEXIST) {
+		huge_page_offset = out_map->addr & (page_size - 1);
+		hpa += huge_page_offset;
+		out_map->level = 0;
+		out_map->pte = pfn_pte(PFN_DOWN(hpa), prot);
+		out_map->pte = pte_mkdirty(out_map->pte);
+
+		ret = kvm_riscv_gstage_set_pte(gstage, pcache, out_map);
+	}
+
+	return ret;
 }
 
 static inline unsigned long make_child_pte(unsigned long huge_pte, int index,
-- 
2.43.0


-- 
kvm-riscv mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/kvm-riscv
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.