* [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves
@ 2026-10-17 3:09 SeungJu Cheon
2026-10-17 3:27 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: SeungJu Cheon @ 2026-10-17 3:09 UTC (permalink / raw)
To: anup, atish.patra
Cc: tjytimi, inochiama, pjw, palmer, aou, alex, andrew.jones,
pbonzini, seanjc, kvm-riscv, kvm, linux-riscv, linux-kernel,
stable, SeungJu Cheon, Sashiko
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 <sashiko-bot@kernel.org>
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+
Signed-off-by: SeungJu Cheon <suunj1331@gmail.com>
---
Testing:
1. HiFive Premier P550
A selftest makes the guest read an untouched page, then write to
it with dirty logging enabled. Before the fix, the write corrupts
the host zero page. After the fix, CoW allocates a private page
and the zero page stays zero.
dirty_log_perf_test -v 3 -b 256M -i 3 shows no noticeable
regression (14-21s per iteration before and after).
2. QEMU (-cpu rv64,h=true)
The same selftest reproduces the zero-page corruption before the
fix and passes after it.
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 | 15 +++++++++++++--
3 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
index aaf080ba1b77..0d19ad6d7dfe 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 be70607a33ea..d9bfd0c6d5fd 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 042d1e3b3547..196cde6c40cd 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)
--
2.52.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves
2026-10-17 3:09 [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves SeungJu Cheon
@ 2026-10-17 3:27 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-17 3:27 UTC (permalink / raw)
To: SeungJu Cheon; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] KVM: riscv: missing host page dirtying in kvm_riscv_mmu_map() on read fault
--
commit 62242a1df73a6632040ee4bbfc1be21570c1f2af
Author: SeungJu Cheon <suunj1331@gmail.com>
KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves
This commit records host writability in RSW bit 9 (KVM_GSTAGE_PTE_HOST_WRITABLE)
on G-stage leaf PTEs before dirty logging forces the mapping read-only, and
checks this bit in kvm_riscv_mmu_dirty_log_write_fault_fast() so that
read-only host mappings fall back to the slow path.
> diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
> index 371fccaf9df08..bbc8ab632c8a1 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;
> @@ -629,6 +635,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;
> @@ -713,6 +720,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.
> @@ -738,10 +747,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)
[Severity: Critical]
Should the out_unlock path in kvm_riscv_mmu_map() also be updated to pass
host_writable instead of writable to kvm_release_faultin_page()?
Currently, the end of kvm_riscv_mmu_map() does:
out_unlock:
kvm_release_faultin_page(kvm, page,
unused || (ret && ret != -EEXIST), writable);
When dirty logging is enabled on a writable memslot (logging == true) and the
guest triggers a read fault (is_write == false) on a page whose primary MMU
PTE already has write permission (such as a clean MAP_SHARED shmem/memfd folio
faulted in for read, or an anonymous folio after MADV_FREE),
__kvm_faultin_pfn() sees pte_write() via get_user_page_fast_only() and sets
writable = true without marking the host PTE or folio dirty.
kvm_riscv_mmu_map() then records host_writable = true, sets writable = false
because (logging && !is_write), and installs a read-only G-stage PTE with
KVM_GSTAGE_PTE_HOST_WRITABLE set. Because writable is false at out_unlock,
kvm_release_faultin_page() calls kvm_release_page_clean(page) instead of
kvm_release_page_dirty(page).
If the guest subsequently writes to that GPA,
kvm_riscv_mmu_dirty_log_write_fault_fast() sees KVM_GSTAGE_PTE_HOST_WRITABLE
and makes the G-stage PTE writable under read_lock(&kvm->mmu_lock) without
marking the backing host folio dirty:
include/linux/kvm_host.h:kvm_release_faultin_page()
/*
* If the page that KVM got from the *primary MMU* is writable, and KVM
* installed or reused a SPTE, mark the page/folio dirty. Note, this
* may mark a folio dirty even if KVM created a read-only SPTE, e.g. if
* the GFN is write-protected. Folios can't be safely marked dirty
* outside of mmu_lock as doing so could race with writeback on the
* folio. As a result, KVM can't mark folios dirty in the fast page
* fault handler, and so KVM must (somewhat) speculatively mark the
* folio dirty if KVM could locklessly make the SPTE writable.
*/
Since G-stage unmapping only clears the PTE without propagating dirty state to
the host struct page, can host page reclaim later evict the guest-modified
folio as clean without writeback or swap-out, losing the guest's written data?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261017030927.345454-1-suunj1331@gmail.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-17 3:27 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-17 3:09 [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves SeungJu Cheon
2026-10-17 3:27 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox