From: David Woodhouse <[email protected]>

The gfn_to_hva_cache init and the pfncache refresh refuse to cache a
range carrying NR/NW attributes, but consulted only plane 0's array.
Cached accesses made in vCPU context for a non-zero plane vCPU (steal
time, PV EOI, pvclock) therefore enforced plane 0's protections only: a
plane-N guest could point a PV MSR at a gfn its own plane marked NW and
kvm_write_guest_offset_cached() wrote through it, while the uncached
paths correctly use the vCPU's own plane.

A cached translation has no plane context at use time, so refuse the
cache when ANY plane protects the range.  The attribute generation that
invalidates these caches is already partition-wide, so updates on any
plane force a re-check.  The plane array walk follows the existing
lock-free kvm->planes[] reader pattern (acquire paired with the
publishing release).

Fixes: a407d03e5697 ("KVM: Take memory protections into account for accesses 
with cached gfn->hva")
Fixes: dc506e2ff987 ("KVM: pfncache: Fail to refresh if it contains memory 
protections")
Signed-off-by: David Woodhouse <[email protected]>
Assisted-by: LLM
---
 include/linux/kvm_host.h |  8 ++++++++
 virt/kvm/kvm_main.c      | 31 ++++++++++++++++++++++++++++++-
 virt/kvm/pfncache.c      |  3 ++-
 3 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 60923494eea5..6a1ac96d64ba 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2846,6 +2846,8 @@ bool kvm_plane_range_has_any_attrs(struct kvm_plane 
*plane, gfn_t start,
                                   gfn_t end);
 bool kvm_range_has_any_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t 
end,
                                        unsigned long mask);
+bool kvm_any_plane_range_has_rw_memory_protections(struct kvm *kvm,
+                                                  gfn_t start, gfn_t end);
 bool kvm_arch_pre_set_memory_attributes(struct kvm *kvm,
                                        struct kvm_gfn_range *range);
 bool kvm_arch_post_set_memory_attributes(struct kvm *kvm,
@@ -2889,6 +2891,12 @@ static inline bool 
kvm_range_has_any_memory_attributes(struct kvm *kvm,
 {
        return false;
 }
+static inline bool kvm_any_plane_range_has_rw_memory_protections(struct kvm 
*kvm,
+                                                                gfn_t start,
+                                                                gfn_t end)
+{
+       return false;
+}
 static inline u64 kvm_mem_attributes_generation(struct kvm *kvm)
 {
        return 0;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 1c70bb70500c..f0b474b0547d 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2779,6 +2779,34 @@ static bool kvm_plane_range_has_memory_attributes(struct 
kvm_plane *plane,
        return false;
 }
 
+/*
+ * Returns true if any plane's attributes deny read or write anywhere in
+ * [@start, @end).  Cached gfn->hva translations carry no plane context
+ * at use time, so a cache must be refused when ANY plane protects the
+ * range, not just plane 0.
+ */
+bool kvm_any_plane_range_has_rw_memory_protections(struct kvm *kvm,
+                                                  gfn_t start, gfn_t end)
+{
+       unsigned long mask = KVM_MEMORY_ATTRIBUTE_NR | KVM_MEMORY_ATTRIBUTE_NW;
+       int i;
+
+       if (kvm_range_has_rw_memory_protections(kvm, start, end))
+               return true;
+
+       for (i = 1; i < KVM_MAX_PLANES; i++) {
+               /* Pairs with the release in kvm_create_plane(). */
+               struct kvm_plane *plane = smp_load_acquire(&kvm->planes[i]);
+
+               if (plane &&
+                   kvm_plane_range_has_memory_attributes(plane, start, end,
+                                                         mask))
+                       return true;
+       }
+
+       return false;
+}
+
 static bool kvm_range_memory_attributes_need_sync(struct kvm_plane *plane,
                                                  gfn_t start, gfn_t end,
                                                  unsigned long attributes)
@@ -3939,7 +3967,8 @@ static int __kvm_gfn_to_hva_cache_init(struct kvm *kvm,
         * check has to happen after storing the generation number.
         */
        ghc->attrs_generation = kvm_mem_attributes_generation(kvm);
-       if (kvm_range_has_rw_memory_protections(kvm, start_gfn, end_gfn + 1)) {
+       if (kvm_any_plane_range_has_rw_memory_protections(kvm, start_gfn,
+                                                         end_gfn + 1)) {
                ghc->hva = KVM_HVA_ERR_BAD;
                return -EFAULT;
        }
diff --git a/virt/kvm/pfncache.c b/virt/kvm/pfncache.c
index 39935740136d..6ad1c62e9cd8 100644
--- a/virt/kvm/pfncache.c
+++ b/virt/kvm/pfncache.c
@@ -304,7 +304,8 @@ static int __kvm_gpc_refresh(struct gfn_to_pfn_cache *gpc, 
gpa_t gpa, unsigned l
                         * generation number.
                         */
                        gpc->attrs_generation = 
kvm_mem_attributes_generation(kvm);
-                       if (kvm_range_has_rw_memory_protections(kvm, gfn, gfn + 
1)) {
+                       if (kvm_any_plane_range_has_rw_memory_protections(kvm, 
gfn,
+                                                                         gfn + 
1)) {
                                gpc->uhva = KVM_HVA_ERR_BAD;
                                ret = -EFAULT;
                                goto out;
-- 
2.50.1


Reply via email to