[PATCH 18/45] KVM: Include memory protections in result of gfn->hva conversion

From: Paolo Bonzini

Date: Mon Nov 02 2026 - 12:57:29 EST


From: Nicolas Saenz Julienne <nsaenz@xxxxxxxxxx>

All paths that lead to guest memory accesses now need to check whether
memory attributes allow that access. Users of gfn_to_hva,
kvm_vcpu_gfn_to_hva and their *_prot variant can get it more or
less for free via erroneous return values, so do this first.

Note however that this is not true of the variants that take a cached
kvm_memslots pointer. These include caches (gfn-to-hva and gfn-to-pfn)
and page faults, both of which will need specific changes; but the more
optimized functions in virt/kvm/kvm_main.c such as kvm_read_guest()
and kvm_write_guest() also retrieve the memslot high in the call chain,
and therefore they will need changes in __kvm_read/write_guest_page().

Signed-off-by: Nicolas Saenz Julienne <nsaenz@xxxxxxxxxx>
Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
Link: https://patch.msgid.link/20260918135030.171564-15-pbonzini@xxxxxxxxxx
Signed-off-by: Paolo Bonzini <pbonzini@xxxxxxxxxx>
---
include/linux/kvm_host.h | 19 ++++++++++++++
virt/kvm/kvm_main.c | 55 +++++++++++++++++++++++++++++++++++++---
2 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 8442fb29a1ee..3791e4290742 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2623,6 +2623,11 @@ static inline u64 kvm_supported_vm_mem_attributes(struct kvm *kvm)
{
return 0;
}
+
+static inline unsigned long kvm_get_vm_memory_attributes(struct kvm *kvm, gfn_t gfn)
+{
+ return 0;
+}
#endif /* CONFIG_KVM_VM_MEMORY_ATTRIBUTES */

#ifdef CONFIG_KVM_PRIVATE_MEMATTR_VM
@@ -2653,6 +2658,20 @@ static inline bool kvm_is_private_gfn(struct kvm *kvm, gfn_t gfn)
}
#endif /* kvm_arch_has_private_mem */

+static inline int kvm_mem_attributes_may_read_gfn(struct kvm *kvm, gfn_t gfn)
+{
+ unsigned long attrs = kvm_get_vm_memory_attributes(kvm, gfn);
+
+ return kvm_mem_attributes_may_read(attrs);
+}
+
+static inline int kvm_mem_attributes_may_write_gfn(struct kvm *kvm, gfn_t gfn)
+{
+ unsigned long attrs = kvm_get_vm_memory_attributes(kvm, gfn);
+
+ return kvm_mem_attributes_may_write(attrs);
+}
+
#ifdef CONFIG_KVM_GUEST_MEMFD
bool kvm_gmem_is_private_gfn(struct kvm *kvm, gfn_t gfn);
bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 717d7aec9a6c..d3f074d7fd6f 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2874,13 +2874,38 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(gfn_to_hva_memslot);

unsigned long gfn_to_hva(struct kvm *kvm, gfn_t gfn)
{
- return gfn_to_hva_many(gfn_to_memslot(kvm, gfn), gfn, NULL);
+ unsigned long addr;
+
+ addr = gfn_to_hva_many(gfn_to_memslot(kvm, gfn), gfn, NULL);
+ if (kvm_is_error_hva(addr))
+ return addr;
+
+ if (!kvm_mem_attributes_may_read_gfn(kvm, gfn))
+ return KVM_HVA_ERR_BAD;
+
+ if (!kvm_mem_attributes_may_write_gfn(kvm, gfn))
+ return KVM_HVA_ERR_RO_BAD;
+
+ return addr;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(gfn_to_hva);

unsigned long kvm_vcpu_gfn_to_hva(struct kvm_vcpu *vcpu, gfn_t gfn)
{
- return gfn_to_hva_many(kvm_vcpu_gfn_to_memslot(vcpu, gfn), gfn, NULL);
+ struct kvm *kvm = vcpu->kvm;
+ unsigned long addr;
+
+ addr = gfn_to_hva_many(kvm_vcpu_gfn_to_memslot(vcpu, gfn), gfn, NULL);
+ if (kvm_is_error_hva(addr))
+ return addr;
+
+ if (!kvm_mem_attributes_may_read_gfn(kvm, gfn))
+ return KVM_HVA_ERR_BAD;
+
+ if (!kvm_mem_attributes_may_write_gfn(kvm, gfn))
+ return KVM_HVA_ERR_RO_BAD;
+
+ return addr;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_gfn_to_hva);

@@ -2906,15 +2931,37 @@ unsigned long gfn_to_hva_memslot_prot(struct kvm_memory_slot *slot,
unsigned long gfn_to_hva_prot(struct kvm *kvm, gfn_t gfn, bool *writable)
{
struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
+ unsigned long addr;

- return gfn_to_hva_memslot_prot(slot, gfn, writable);
+ addr = gfn_to_hva_memslot_prot(slot, gfn, writable);
+ if (kvm_is_error_hva(addr))
+ return addr;
+
+ if (!kvm_mem_attributes_may_read_gfn(kvm, gfn))
+ return KVM_HVA_ERR_BAD;
+
+ if (writable && !kvm_mem_attributes_may_write_gfn(kvm, gfn))
+ *writable = false;
+
+ return addr;
}

unsigned long kvm_vcpu_gfn_to_hva_prot(struct kvm_vcpu *vcpu, gfn_t gfn, bool *writable)
{
struct kvm_memory_slot *slot = kvm_vcpu_gfn_to_memslot(vcpu, gfn);
+ unsigned long addr;

- return gfn_to_hva_memslot_prot(slot, gfn, writable);
+ addr = gfn_to_hva_memslot_prot(slot, gfn, writable);
+ if (kvm_is_error_hva(addr))
+ return addr;
+
+ if (!kvm_mem_attributes_may_read_gfn(vcpu->kvm, gfn))
+ return KVM_HVA_ERR_BAD;
+
+ if (writable && !kvm_mem_attributes_may_write_gfn(vcpu->kvm, gfn))
+ *writable = false;
+
+ return addr;
}

static bool kvm_is_ad_tracked_page(struct page *page)
--
2.52.0