From: Nicolas Saenz Julienne Account for memory attributes when accessing guest memory through kvm_get/put_guest(). This requires tracking the memory attributes generation as part of gfn_to_hva_cache's data, invalidate the cached information if the generation changes, and failing to refresh the cache if restrictive memory attributes are found within the GPA range. Similar to how gfn_to_hva_cache disallows caching gfns mapped within read-only memory slots, gfns marked as read-only by memory attributes will also fail to initialize. Unsurprisingly, the same behaviour applies to gfns mapped as non-accessible (NR/NW), while non-executable mappings are okay. Signed-off-by: Nicolas Saenz Julienne Signed-off-by: Paolo Bonzini --- include/linux/kvm_host.h | 20 +++++++++++++++++--- virt/kvm/kvm_main.c | 15 +++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index ab62ce37bc18..e9c0932f150e 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1359,7 +1359,8 @@ int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc, typeof(v) __user *__uaddr = (typeof(__uaddr))(__addr + offset); \ int __ret = -EFAULT; \ \ - if (!kvm_is_error_hva(__addr)) \ + if (!kvm_is_error_hva(__addr) && \ + kvm_mem_attributes_may_read_gfn(kvm, gfn)) \ __ret = get_user(v, __uaddr); \ __ret; \ }) @@ -1379,7 +1380,8 @@ int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc, typeof(v) __user *__uaddr = (typeof(__uaddr))(__addr + offset); \ int __ret = -EFAULT; \ \ - if (!kvm_is_error_hva(__addr)) \ + if (!kvm_is_error_hva(__addr) && \ + kvm_mem_attributes_may_write_gfn(kvm, gfn)) \ __ret = put_user(v, __uaddr); \ if (!__ret) \ mark_page_dirty(kvm, gfn); \ @@ -2647,6 +2649,12 @@ static inline unsigned long kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn { return 0; } +static inline bool kvm_range_has_any_memory_attributes(struct kvm *kvm, + gfn_t start, gfn_t end, + unsigned long mask) +{ + return false; +} static inline u64 kvm_mem_attributes_generation(struct kvm *kvm) { return 0; @@ -2667,6 +2675,13 @@ static inline int kvm_mem_attributes_may_write_gfn(struct kvm *kvm, gfn_t gfn) return kvm_mem_attributes_may_write(attrs); } +static inline bool kvm_range_has_rw_memory_protections(struct kvm *kvm, + gfn_t start, gfn_t end) +{ + return kvm_range_has_any_memory_attributes(kvm, start, end, + KVM_MEMORY_ATTRIBUTE_NR | KVM_MEMORY_ATTRIBUTE_NW); +} + static inline bool kvm_memslots_check_gen(struct kvm *kvm, u64 slots_generation, u64 attrs_generation, struct kvm_memslots **p_slots) { @@ -2682,7 +2697,6 @@ static inline bool kvm_check_gen(struct kvm *kvm, u64 slots_generation, return kvm_memslots_check_gen(kvm, slots_generation, attrs_generation, &slots); } - #ifdef CONFIG_KVM_GUEST_MEMFD int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, gfn_t gfn, kvm_pfn_t *pfn, struct page **page, diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index ea9f3e0625cb..24f46dce192e 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -3550,15 +3550,22 @@ static int __kvm_gfn_to_hva_cache_init(struct kvm *kvm, * If the requested region crosses two memslots, we still * verify that the entire region is valid here. */ - for ( ; start_gfn <= end_gfn; start_gfn += nr_pages_avail) { - ghc->memslot = __gfn_to_memslot(slots, start_gfn); - ghc->hva = gfn_to_hva_many(ghc->memslot, start_gfn, - &nr_pages_avail); + for (gfn_t gfn = start_gfn ; gfn <= end_gfn; gfn += nr_pages_avail) { + ghc->memslot = __gfn_to_memslot(slots, gfn); + ghc->hva = gfn_to_hva_many(ghc->memslot, gfn, &nr_pages_avail); if (kvm_is_error_hva(ghc->hva)) return -EFAULT; } + /* + * RW memory attributes are incompatible with GHC. The RW protection + * 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)) { + ghc->hva = KVM_HVA_ERR_BAD; + return -EFAULT; + } /* Use the slow path for cross page reads and writes. */ if (nr_pages_needed == 1) -- 2.52.0