* [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves
@ 2026-10-17 3:09 SeungJu Cheon
2026-10-23 11:57 ` Jinyu Tang
2026-11-06 2:03 ` Inochi Amaoto
0 siblings, 2 replies; 5+ 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] 5+ 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-23 11:57 ` Jinyu Tang 2026-11-06 9:53 ` SeungJu Cheon 2026-11-06 2:03 ` Inochi Amaoto 1 sibling, 1 reply; 5+ messages in thread From: Jinyu Tang @ 2026-10-23 11:57 UTC (permalink / raw) To: SeungJu Cheon Cc: anup, atish.patra, tjytimi, inochiama, pjw, palmer, aou, alex, andrew.jones, pbonzini, seanjc, kvm-riscv, kvm, linux-riscv, linux-kernel, stable, sashiko-bot On Sat, Oct 17, 2026 at 12:09:26PM +0900, SeungJu Cheon wrote: > + /* Let the slow path handle host-side write protection and CoW. */ > + if (!(pte_val(old_pte) & KVM_GSTAGE_PTE_HOST_WRITABLE)) { > + ret = false; > + break; > + } Thanks for fixing this. Tested on QEMU 10.2.1 with a private memfd mapping and dirty logging. A guest read followed by a write corrupts the shared backing without this fix; and with it, CoW occurs and the backing stays unchanged. Reviewed-by: Jinyu Tang <jinyu.tang@linux.dev> Thanks, Jinyu ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves 2026-10-23 11:57 ` Jinyu Tang @ 2026-11-06 9:53 ` SeungJu Cheon 0 siblings, 0 replies; 5+ messages in thread From: SeungJu Cheon @ 2026-11-06 9:53 UTC (permalink / raw) To: Jinyu Tang Cc: anup, atish.patra, tjytimi, inochiama, pjw, palmer, aou, alex, andrew.jones, pbonzini, seanjc, kvm-riscv, kvm, linux-riscv, linux-kernel, stable, sashiko-bot On Fri, Oct 23, 2026 at 8:57 PM Jinyu Tang <jinyu.tang@linux.dev> wrote: > Tested on QEMU 10.2.1 with a private memfd mapping and dirty logging. > A guest read followed by a write corrupts the shared backing without > this fix; and with it, CoW occurs and the backing stays unchanged. > > Reviewed-by: Jinyu Tang <jinyu.tang@linux.dev> > > Thanks, > Jinyu Thanks for testing this with a private memfd mapping and for the review. I'll include the additional fix pointed out by Sashiko and Inochi in v2 and carry your Reviewed-by, unless you'd rather take another look. Thanks, SeungJu ^ permalink raw reply [flat|nested] 5+ 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-23 11:57 ` Jinyu Tang @ 2026-11-06 2:03 ` Inochi Amaoto 2026-11-06 9:49 ` SeungJu Cheon 1 sibling, 1 reply; 5+ messages in thread From: Inochi Amaoto @ 2026-11-06 2:03 UTC (permalink / raw) To: SeungJu Cheon, anup, atish.patra Cc: tjytimi, inochiama, pjw, palmer, aou, alex, andrew.jones, pbonzini, seanjc, kvm-riscv, kvm, linux-riscv, linux-kernel, stable, Sashiko On Sat, Oct 17, 2026 at 12:09:26PM +0900, SeungJu Cheon wrote: > 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 > I guess this fix miss a part: diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c index 53bb25a5a60b..1b35ab980a10 100644 --- a/arch/riscv/kvm/mmu.c +++ b/arch/riscv/kvm/mmu.c @@ -761,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; } Regards, Inochi ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] KVM: riscv: Only fast-path dirty-log write faults on host-writable leaves 2026-11-06 2:03 ` Inochi Amaoto @ 2026-11-06 9:49 ` SeungJu Cheon 0 siblings, 0 replies; 5+ messages in thread From: SeungJu Cheon @ 2026-11-06 9:49 UTC (permalink / raw) To: Inochi Amaoto Cc: anup, atish.patra, tjytimi, pjw, palmer, aou, alex, andrew.jones, pbonzini, seanjc, kvm-riscv, kvm, linux-riscv, linux-kernel, stable, Sashiko On Fri, Nov 6, 2026 at 11:03 AM Inochi Amaoto <inochiama@gmail.com> wrote: > I guess this fix miss a part: > > diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c > index 53bb25a5a60b..1b35ab980a10 100644 > --- a/arch/riscv/kvm/mmu.c > +++ b/arch/riscv/kvm/mmu.c > @@ -761,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; > } > > Regards, > Inochi Thanks for the review. Sashiko also caught this shortly after v1 was posted. I'll include the fix in v2 and send it shortly. Thanks, SeungJu ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-06 9:54 UTC | newest] Thread overview: 5+ 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-23 11:57 ` Jinyu Tang 2026-11-06 9:53 ` SeungJu Cheon 2026-11-06 2:03 ` Inochi Amaoto 2026-11-06 9:49 ` SeungJu Cheon
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox