Message ID | 20220310140911.50924-7-chao.p.peng@linux.intel.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | KVM: mm: fd-based approach for supporting KVM guest private memory | expand |
On Thu, Mar 10, 2022, Chao Peng wrote: > @@ -4476,14 +4477,23 @@ static long kvm_vm_ioctl(struct file *filp, > break; > } > case KVM_SET_USER_MEMORY_REGION: { > - struct kvm_userspace_memory_region kvm_userspace_mem; > + struct kvm_userspace_memory_region_ext region_ext; It's probably a good idea to zero initialize the full region to avoid consuming garbage stack data if there's a bug and an _ext field is accessed without first checking KVM_MEM_PRIVATE. I'm usually opposed to unnecessary initialization, but this seems like something we could screw up quite easily. > r = -EFAULT; > - if (copy_from_user(&kvm_userspace_mem, argp, > - sizeof(kvm_userspace_mem))) > + if (copy_from_user(®ion_ext, argp, > + sizeof(struct kvm_userspace_memory_region))) > goto out; > + if (region_ext.region.flags & KVM_MEM_PRIVATE) { > + int offset = offsetof( > + struct kvm_userspace_memory_region_ext, > + private_offset); > + if (copy_from_user(®ion_ext.private_offset, > + argp + offset, > + sizeof(region_ext) - offset)) In this patch, KVM_MEM_PRIVATE should result in an -EINVAL as it's not yet supported. Copying the _ext on KVM_MEM_PRIVATE belongs in the "Expose KVM_MEM_PRIVATE" patch. Mechnically, what about first reading flags via get_user(), and then doing a single copy_from_user()? It's technically more work in the common case, and requires an extra check to guard against TOCTOU attacks, but this isn't a fast path by any means and IMO the end result makes it easier to understand the relationship between KVM_MEM_PRIVATE and the two different structs. E.g. case KVM_SET_USER_MEMORY_REGION: { struct kvm_user_mem_region region; unsigned long size; u32 flags; memset(®ion, 0, sizeof(region)); r = -EFAULT; if (get_user(flags, (u32 __user *)(argp + offsetof(typeof(region), flags)))) goto out; if (flags & KVM_MEM_PRIVATE) size = sizeof(struct kvm_userspace_memory_region_ext); else size = sizeof(struct kvm_userspace_memory_region); if (copy_from_user(®ion, argp, size)) goto out; r = -EINVAL; if ((flags ^ region.flags) & KVM_MEM_PRIVATE) goto out; r = kvm_vm_ioctl_set_memory_region(kvm, ®ion); break; } > + goto out; > + } > > - r = kvm_vm_ioctl_set_memory_region(kvm, &kvm_userspace_mem); > + r = kvm_vm_ioctl_set_memory_region(kvm, ®ion_ext); > break; > } > case KVM_GET_DIRTY_LOG: { > -- > 2.17.1 >
On Mon, Mar 28, 2022 at 10:26:55PM +0000, Sean Christopherson wrote: > On Thu, Mar 10, 2022, Chao Peng wrote: > > @@ -4476,14 +4477,23 @@ static long kvm_vm_ioctl(struct file *filp, > > break; > > } > > case KVM_SET_USER_MEMORY_REGION: { > > - struct kvm_userspace_memory_region kvm_userspace_mem; > > + struct kvm_userspace_memory_region_ext region_ext; > > It's probably a good idea to zero initialize the full region to avoid consuming > garbage stack data if there's a bug and an _ext field is accessed without first > checking KVM_MEM_PRIVATE. I'm usually opposed to unnecessary initialization, but > this seems like something we could screw up quite easily. > > > r = -EFAULT; > > - if (copy_from_user(&kvm_userspace_mem, argp, > > - sizeof(kvm_userspace_mem))) > > + if (copy_from_user(®ion_ext, argp, > > + sizeof(struct kvm_userspace_memory_region))) > > goto out; > > + if (region_ext.region.flags & KVM_MEM_PRIVATE) { > > + int offset = offsetof( > > + struct kvm_userspace_memory_region_ext, > > + private_offset); > > + if (copy_from_user(®ion_ext.private_offset, > > + argp + offset, > > + sizeof(region_ext) - offset)) > > In this patch, KVM_MEM_PRIVATE should result in an -EINVAL as it's not yet > supported. Copying the _ext on KVM_MEM_PRIVATE belongs in the "Expose KVM_MEM_PRIVATE" > patch. Agreed. > > Mechnically, what about first reading flags via get_user(), and then doing a single > copy_from_user()? It's technically more work in the common case, and requires an > extra check to guard against TOCTOU attacks, but this isn't a fast path by any means > and IMO the end result makes it easier to understand the relationship between > KVM_MEM_PRIVATE and the two different structs. Will use this code, thanks for typing. Chao > > E.g. > > case KVM_SET_USER_MEMORY_REGION: { > struct kvm_user_mem_region region; > unsigned long size; > u32 flags; > > memset(®ion, 0, sizeof(region)); > > r = -EFAULT; > if (get_user(flags, (u32 __user *)(argp + offsetof(typeof(region), flags)))) > goto out; > > if (flags & KVM_MEM_PRIVATE) > size = sizeof(struct kvm_userspace_memory_region_ext); > else > size = sizeof(struct kvm_userspace_memory_region); > if (copy_from_user(®ion, argp, size)) > goto out; > > r = -EINVAL; > if ((flags ^ region.flags) & KVM_MEM_PRIVATE) > goto out; > > r = kvm_vm_ioctl_set_memory_region(kvm, ®ion); > break; > } > > > + goto out; > > + } > > > > - r = kvm_vm_ioctl_set_memory_region(kvm, &kvm_userspace_mem); > > + r = kvm_vm_ioctl_set_memory_region(kvm, ®ion_ext); > > break; > > } > > case KVM_GET_DIRTY_LOG: { > > -- > > 2.17.1 > >
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 8c06b8204fca..1d9dbef67715 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -11757,13 +11757,13 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa, } for (i = 0; i < KVM_ADDRESS_SPACE_NUM; i++) { - struct kvm_userspace_memory_region m; + struct kvm_userspace_memory_region_ext m; - m.slot = id | (i << 16); - m.flags = 0; - m.guest_phys_addr = gpa; - m.userspace_addr = hva; - m.memory_size = size; + m.region.slot = id | (i << 16); + m.region.flags = 0; + m.region.guest_phys_addr = gpa; + m.region.userspace_addr = hva; + m.region.memory_size = size; r = __kvm_set_memory_region(kvm, &m); if (r < 0) return ERR_PTR_USR(r); diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 3be8116079d4..c92c70174248 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -1082,9 +1082,9 @@ enum kvm_mr_change { }; int kvm_set_memory_region(struct kvm *kvm, - const struct kvm_userspace_memory_region *mem); + const struct kvm_userspace_memory_region_ext *region_ext); int __kvm_set_memory_region(struct kvm *kvm, - const struct kvm_userspace_memory_region *mem); + const struct kvm_userspace_memory_region_ext *region_ext); void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot); void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen); int kvm_arch_prepare_memory_region(struct kvm *kvm, diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 69c318fdff61..d11a2628b548 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -1809,8 +1809,9 @@ static bool kvm_check_memslot_overlap(struct kvm_memslots *slots, int id, * Must be called holding kvm->slots_lock for write. */ int __kvm_set_memory_region(struct kvm *kvm, - const struct kvm_userspace_memory_region *mem) + const struct kvm_userspace_memory_region_ext *region_ext) { + const struct kvm_userspace_memory_region *mem = ®ion_ext->region; struct kvm_memory_slot *old, *new; struct kvm_memslots *slots; enum kvm_mr_change change; @@ -1913,24 +1914,24 @@ int __kvm_set_memory_region(struct kvm *kvm, EXPORT_SYMBOL_GPL(__kvm_set_memory_region); int kvm_set_memory_region(struct kvm *kvm, - const struct kvm_userspace_memory_region *mem) + const struct kvm_userspace_memory_region_ext *region_ext) { int r; mutex_lock(&kvm->slots_lock); - r = __kvm_set_memory_region(kvm, mem); + r = __kvm_set_memory_region(kvm, region_ext); mutex_unlock(&kvm->slots_lock); return r; } EXPORT_SYMBOL_GPL(kvm_set_memory_region); static int kvm_vm_ioctl_set_memory_region(struct kvm *kvm, - struct kvm_userspace_memory_region *mem) + struct kvm_userspace_memory_region_ext *region_ext) { - if ((u16)mem->slot >= KVM_USER_MEM_SLOTS) + if ((u16)region_ext->region.slot >= KVM_USER_MEM_SLOTS) return -EINVAL; - return kvm_set_memory_region(kvm, mem); + return kvm_set_memory_region(kvm, region_ext); } #ifndef CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT @@ -4476,14 +4477,23 @@ static long kvm_vm_ioctl(struct file *filp, break; } case KVM_SET_USER_MEMORY_REGION: { - struct kvm_userspace_memory_region kvm_userspace_mem; + struct kvm_userspace_memory_region_ext region_ext; r = -EFAULT; - if (copy_from_user(&kvm_userspace_mem, argp, - sizeof(kvm_userspace_mem))) + if (copy_from_user(®ion_ext, argp, + sizeof(struct kvm_userspace_memory_region))) goto out; + if (region_ext.region.flags & KVM_MEM_PRIVATE) { + int offset = offsetof( + struct kvm_userspace_memory_region_ext, + private_offset); + if (copy_from_user(®ion_ext.private_offset, + argp + offset, + sizeof(region_ext) - offset)) + goto out; + } - r = kvm_vm_ioctl_set_memory_region(kvm, &kvm_userspace_mem); + r = kvm_vm_ioctl_set_memory_region(kvm, ®ion_ext); break; } case KVM_GET_DIRTY_LOG: {