summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJinyu Tang <tjytimi@163.com>2026-06-04 22:26:01 +0800
committerAnup Patel <anup@brainfault.org>2026-06-07 10:54:40 +0530
commit49476d58f2171afc2e899da8040710d2c37760af (patch)
tree4dcca1c3f5745acec8eb4c9890328dcff86d0d58
parent64e50ab0ed84383eec398ced43b8ea40b249804b (diff)
KVM: riscv: Check hugetlb block mappings against memslot bounds
RISC-V KVM has used the hugetlb VMA size directly as the G-stage mapping size since stage-2 page table support was added. That is safe only if the block covered by the fault is fully contained in the memslot and the userspace address has the same offset as the GPA within that block. The THP path already checks those constraints before installing a PMD block mapping. The hugetlb path did not, so an unaligned memslot could make KVM install a PMD or PUD sized G-stage block that covers memory outside the slot or maps the wrong host pages. Pass the target mapping size into fault_supports_gstage_huge_mapping(). The same helper can be used for both THP PMD mappings and hugetlb PMD/PUD mappings. Select hugetlb mapping sizes through the same memslot-boundary check, falling back from PUD to PMD to PAGE_SIZE. When a smaller hugetlb mapping size is selected, fault the GFN aligned to that selected size instead of the original VMA size. Also keep hugetlb mappings out of transparent_hugepage_adjust(). Once the hugetlb path has chosen PAGE_SIZE, promoting it again through the THP helper would miss the hugetlb fallback decision. Fixes: 9d05c1fee837 ("RISC-V: KVM: Implement stage2 page table programming") Signed-off-by: Jinyu Tang <tjytimi@163.com> Reviewed-by: Nutty Liu <nutty.liu@hotmail.com> Reviewed-by: Anup Patel <anup@brainfault.org> Link: https://lore.kernel.org/r/20260604142602.3582602-2-tjytimi@163.com Signed-off-by: Anup Patel <anup@brainfault.org>
-rw-r--r--arch/riscv/kvm/mmu.c56
1 files changed, 46 insertions, 10 deletions
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 38d3eaa1c5e5..082f9b261733 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -302,7 +302,8 @@ bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
}
static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
- unsigned long hva)
+ unsigned long hva,
+ unsigned long map_size)
{
hva_t uaddr_start, uaddr_end;
gpa_t gpa_start;
@@ -316,8 +317,8 @@ static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
/*
* Pages belonging to memslots that don't have the same alignment
- * within a PMD for userspace and GPA cannot be mapped with g-stage
- * PMD entries, because we'll end up mapping the wrong pages.
+ * within a huge page for userspace and GPA cannot be mapped with
+ * g-stage block entries, because we'll end up mapping the wrong pages.
*
* Consider a layout like the following:
*
@@ -337,7 +338,7 @@ static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
* e -> g
* f -> h
*/
- if ((gpa_start & (PMD_SIZE - 1)) != (uaddr_start & (PMD_SIZE - 1)))
+ if ((gpa_start & (map_size - 1)) != (uaddr_start & (map_size - 1)))
return false;
/*
@@ -352,7 +353,8 @@ static bool fault_supports_gstage_huge_mapping(struct kvm_memory_slot *memslot,
* userspace_addr or the base_gfn, as both are equally aligned (per
* the check above) and equally sized.
*/
- return (hva >= ALIGN(uaddr_start, PMD_SIZE)) && (hva < ALIGN_DOWN(uaddr_end, PMD_SIZE));
+ return (hva >= ALIGN(uaddr_start, map_size)) &&
+ (hva < ALIGN_DOWN(uaddr_end, map_size));
}
static int get_hva_mapping_size(struct kvm *kvm,
@@ -420,7 +422,7 @@ static unsigned long transparent_hugepage_adjust(struct kvm *kvm,
* sure that the HVA and GPA are sufficiently aligned and that the
* block map is contained within the memslot.
*/
- if (fault_supports_gstage_huge_mapping(memslot, hva)) {
+ if (fault_supports_gstage_huge_mapping(memslot, hva, PMD_SIZE)) {
int sz;
sz = get_hva_mapping_size(kvm, hva);
@@ -437,6 +439,28 @@ static unsigned long transparent_hugepage_adjust(struct kvm *kvm,
return PAGE_SIZE;
}
+static unsigned long hugetlb_mapping_size(struct kvm_memory_slot *memslot,
+ unsigned long hva,
+ unsigned long map_size)
+{
+ switch (map_size) {
+#ifndef CONFIG_32BIT
+ case PUD_SIZE:
+ if (fault_supports_gstage_huge_mapping(memslot, hva, PUD_SIZE))
+ return PUD_SIZE;
+ fallthrough;
+#endif
+ case PMD_SIZE:
+ if (fault_supports_gstage_huge_mapping(memslot, hva, PMD_SIZE))
+ return PMD_SIZE;
+ fallthrough;
+ case PAGE_SIZE:
+ return PAGE_SIZE;
+ default:
+ return map_size;
+ }
+}
+
static bool kvm_riscv_mmu_dirty_log_write_fault_fast(struct kvm *kvm,
struct kvm_memory_slot *memslot,
gpa_t gpa,
@@ -514,6 +538,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
{
int ret;
kvm_pfn_t hfn;
+ bool is_hugetlb;
bool writable;
short vma_pageshift;
gfn_t gfn = gpa >> PAGE_SHIFT;
@@ -551,16 +576,23 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
return -EFAULT;
}
- if (is_vm_hugetlb_page(vma))
+ is_hugetlb = is_vm_hugetlb_page(vma);
+ if (is_hugetlb)
vma_pageshift = huge_page_shift(hstate_vma(vma));
else
vma_pageshift = PAGE_SHIFT;
vma_pagesize = 1ULL << vma_pageshift;
if (logging || (vma->vm_flags & VM_PFNMAP))
vma_pagesize = PAGE_SIZE;
+ else if (is_hugetlb)
+ vma_pagesize = hugetlb_mapping_size(memslot, hva, vma_pagesize);
+ /*
+ * For hugetlb mappings, vma_pagesize might have been reduced from the
+ * VMA size to a smaller safe mapping size.
+ */
if (vma_pagesize == PMD_SIZE || vma_pagesize == PUD_SIZE)
- gfn = (gpa & huge_page_mask(hstate_vma(vma))) >> PAGE_SHIFT;
+ gfn = ALIGN_DOWN(gpa, vma_pagesize) >> PAGE_SHIFT;
/*
* Read mmu_invalidate_seq so that KVM can detect if the results of
@@ -602,8 +634,12 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
if (mmu_invalidate_retry(kvm, mmu_seq))
goto out_unlock;
- /* Check if we are backed by a THP and thus use block mapping if possible */
- if (!logging && (vma_pagesize == PAGE_SIZE))
+ /*
+ * Check if we are backed by a THP and thus use block mapping if
+ * possible. Hugetlb mappings already selected their target size above,
+ * so do not promote them through the THP helper.
+ */
+ if (!logging && !is_hugetlb && vma_pagesize == PAGE_SIZE)
vma_pagesize = transparent_hugepage_adjust(kvm, memslot, hva, &hfn, &gpa);
if (writable) {