Use a separate VMA for each address space when installing internal memslots, i.e. mmap() a separate VMA for the SMM memslot, and delete the TODO along with the hack to only unmap() the SMM memslot when freeing memslots. Use an explicit shmem file (as opposed to the implicit shmem file created for an anonymous MAP_SHARED mapping) so that both memslots' VMAs point at the same backing page.
Note, using shmem_kernel_file_setup() directly is more or less equivalent to shmem_zero_setup(), which is used for "MAP_SHARED | MAP_ANONYMOUS" mappings; the only practical difference is the dentry name. Return the virtual address of the non-SMM memslot mapping, as that memslot is guaranteed to exist. Signed-off-by: Sean Christopherson <[email protected]> --- arch/x86/kvm/x86.c | 62 +++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 0b94f981a2e1..8cb58040dd81 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -59,6 +59,7 @@ #include <linux/irqbypass.h> #include <linux/sched/stat.h> #include <linux/sched/isolation.h> +#include <linux/shmem_fs.h> #include <linux/mem_encrypt.h> #include <linux/suspend.h> #include <linux/smp.h> @@ -10007,9 +10008,19 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type) } static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id, - gpa_t gpa, unsigned long hva, u32 size) + gpa_t gpa, struct file *f, u32 size) { struct kvm_userspace_memory_region2 m; + unsigned long hva; + int r; + + if (f) { + hva = vm_mmap(f, 0, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0); + if (IS_ERR_VALUE(hva)) + return (long)hva; + } else { + hva = 0; + } if (!size && !id_to_memslot(__kvm_memslots(kvm, as_id), id)) return 0; @@ -10019,7 +10030,11 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id, m.guest_phys_addr = gpa; m.userspace_addr = hva; m.memory_size = size; - return kvm_set_internal_memslot(kvm, &m); + + r = kvm_set_internal_memslot(kvm, &m); + if (r && f) + vm_munmap(hva, size); + return r; } /** @@ -10047,7 +10062,8 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id, void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa, u32 size) { - unsigned long hva; + struct file *f __free(fput) = NULL; + struct kvm_memory_slot *slot; int i, r; lockdep_assert_held(&kvm->slots_lock); @@ -10055,37 +10071,31 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa, if (WARN_ON(id >= KVM_MEM_SLOTS_NUM)) return ERR_PTR_USR(-EINVAL); - if (size && id_to_memslot(kvm_memslots(kvm), id)) - return ERR_PTR_USR(-EEXIST); - if (size) { - /* - * MAP_SHARED to prevent internal slot pages from being moved - * by fork()/COW. - */ - hva = vm_mmap(NULL, 0, size, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, 0); - if (IS_ERR_VALUE(hva)) - return (void __user *)hva; - } else { - hva = 0; + if (id_to_memslot(kvm_memslots(kvm), id)) + return ERR_PTR_USR(-EEXIST); + + f = shmem_kernel_file_setup("kvm_memslot", size, EMPTY_VMA_FLAGS); + if (IS_ERR(f)) + return (void __user *)f; } for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) { - r = x86_set_internal_memslot(kvm, id, i, gpa, hva, size); + r = x86_set_internal_memslot(kvm, id, i, gpa, f, size); if (!r) continue; - if (size) { - for (i--; i >= 0; i--) - x86_set_internal_memslot(kvm, id, i, gpa, 0, 0); + for (i--; i >= 0 && size; i--) + x86_set_internal_memslot(kvm, id, i, gpa, NULL, 0); - vm_munmap(hva, size); - } return ERR_PTR_USR(r); } - return (void __user *)hva; + slot = id_to_memslot(kvm_memslots(kvm), id); + if (WARN_ON_ONCE(!!size != !!slot)) + return ERR_PTR_USR(-EIO); + + return (void __user *)(slot ? slot->userspace_addr : 0); } EXPORT_SYMBOL_FOR_KVM_INTERNAL(__x86_set_memory_region); @@ -10158,12 +10168,8 @@ void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot) * unless the memory map has changed due to process exit or fd copying. * Leak the mapping on failure, e.g. if the task is killed. Worst case * scenario, the page(s) will be reclaimed when the process exits. - * - * TODO: Allocate VMAs for each address space and drop the check on the - * as_id, which depends on freeing memslots from 0=>N-1. */ - if (current->mm == kvm->mm && slot->id >= KVM_USER_MEM_SLOTS && - slot->as_id == (kvm_arch_nr_memslot_as_ids(kvm) - 1)) + if (current->mm == kvm->mm && slot->id >= KVM_USER_MEM_SLOTS) vm_munmap(slot->userspace_addr, slot->npages * PAGE_SIZE); } -- 2.56.0.385.gac671c819a-goog
