The dirty-log write fault fast path sets _PAGE_WRITE without checking whether the backing page is host-writable. A read fault can install a read-only mapping to the shared zero page. A subsequent guest write then takes the fast path and writes into the zero page instead of going through the slow path to break CoW. Use RSW bit 9 to record host writability before dirty logging forces the mapping read-only. Preserve this bit across write-protection and fall back to the slow path when it is clear. Reported-by: Sashiko Closes: https://lore.kernel.org/all/sashiko-outbox-174168@kernel.org/ Fixes: 7705be59eb2d ("KVM: riscv: Fast-path dirty logging write faults") Cc: stable@vger.kernel.org # 7.2+ Reviewed-by: Jinyu Tang Signed-off-by: SeungJu Cheon --- v2: - Pass host_writable to kvm_release_faultin_page() as well (Inochi Amaoto, Sashiko) v1: https://lore.kernel.org/all/20261017030927.345454-1-suunj1331@gmail.com/ Testing (v2, QEMU -cpu rv64,h=true, 4 vCPUs): A selftest makes the guest read an untouched page, then write to it with dirty logging enabled. Before the fix, the host zero page is corrupted (0x5a5a5a5a5a5a5a5a != 0). After the fix, CoW allocates a private page and the zero page stays zero. dirty_log_perf_test -v 2 -b 128M -i 3 completes for Sv39/Sv48/Sv57 with no warnings in dmesg. v1 was additionally tested on a HiFive Premier P550 (no noticeable regression in dirty_log_perf_test -v 3 -b 256M -i 3). The board is not available for v2. This patch applies on top of the Accessed-bit fix [1]. [1] https://lore.kernel.org/all/20261016101458.221648-1-suunj1331@gmail.com/ arch/riscv/include/asm/kvm_gstage.h | 5 ++++- arch/riscv/kvm/gstage.c | 5 ++++- arch/riscv/kvm/mmu.c | 17 ++++++++++++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h index aaf080ba1b7..0d19ad6d7df 100644 --- a/arch/riscv/include/asm/kvm_gstage.h +++ b/arch/riscv/include/asm/kvm_gstage.h @@ -35,6 +35,9 @@ extern unsigned long kvm_riscv_gstage_max_pgd_levels; #define kvm_riscv_gstage_pgd_xbits 2 #define kvm_riscv_gstage_pgd_size (1UL << (HGATP_PAGE_SHIFT + kvm_riscv_gstage_pgd_xbits)) +/* RSW bit 9: the host mapping behind this leaf is writable. */ +#define KVM_GSTAGE_PTE_HOST_WRITABLE BIT(9) + static inline unsigned long kvm_riscv_gstage_gpa_bits(unsigned long pgd_levels) { return (HGATP_PAGE_SHIFT + @@ -61,7 +64,7 @@ bool kvm_riscv_gstage_try_update_pte(struct kvm_gstage *gstage, u32 level, int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage, struct kvm_mmu_memory_cache *pcache, gpa_t gpa, phys_addr_t hpa, unsigned long page_size, - bool page_rdonly, bool page_exec, + bool page_rdonly, bool page_exec, bool host_writable, struct kvm_gstage_mapping *out_map); bool kvm_riscv_gstage_split_huge(struct kvm_gstage *gstage, diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c index be70607a33e..d9bfd0c6d5f 100644 --- a/arch/riscv/kvm/gstage.c +++ b/arch/riscv/kvm/gstage.c @@ -207,7 +207,7 @@ static void kvm_riscv_gstage_update_pte_prot(struct kvm_gstage *gstage, u32 leve int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage, struct kvm_mmu_memory_cache *pcache, gpa_t gpa, phys_addr_t hpa, unsigned long page_size, - bool page_rdonly, bool page_exec, + bool page_rdonly, bool page_exec, bool host_writable, struct kvm_gstage_mapping *out_map) { bool found_leaf; @@ -246,6 +246,9 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage, prot = PAGE_WRITE; } + if (host_writable) + prot = __pgprot(pgprot_val(prot) | KVM_GSTAGE_PTE_HOST_WRITABLE); + found_leaf = kvm_riscv_gstage_get_leaf(gstage, gpa, &ptep, &ptep_level); if (found_leaf) { /* diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index 5664383a1a3..b41534dd500 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -586,6 +586,12 @@ static bool kvm_riscv_mmu_dirty_log_write_fault_fast(struct kvm *kvm, break; } + /* Let the slow path handle host-side write protection and CoW. */ + if (!(pte_val(old_pte) & KVM_GSTAGE_PTE_HOST_WRITABLE)) { + ret = false; + break; + } + if (!dirty_marked) { mark_page_dirty_in_slot(kvm, memslot, gfn); dirty_marked = true; @@ -630,6 +636,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, bool is_hugetlb; bool unused = false; bool writable; + bool host_writable; unsigned int vma_pageshift; gfn_t gfn = gpa >> PAGE_SHIFT; struct vm_area_struct *vma; @@ -714,6 +721,8 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, if (is_error_noslot_pfn(hfn)) return -EFAULT; + host_writable = writable; + /* * If logging is active then we allow writable pages only * for write faults. @@ -739,10 +748,12 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, if (writable) { 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); + vma_pagesize, false, true, host_writable, + out_map); } else { ret = kvm_riscv_gstage_map_page(&gstage, pcache, gpa, hfn << PAGE_SHIFT, - vma_pagesize, true, true, out_map); + vma_pagesize, true, true, host_writable, + out_map); } if (ret) @@ -750,7 +761,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot, out_unlock: kvm_release_faultin_page(kvm, page, - unused || (ret && ret != -EEXIST), writable); + unused || (ret && ret != -EEXIST), host_writable); write_unlock(&kvm->mmu_lock); return ret; } base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4 prerequisite-patch-id: aea68332f05283074bb715e7dde093a2a316a51a -- 2.52.0