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