KVM guest_memfd changes for 7.2

- Return -EEXIST instead of -EINVAL if userspace attempts to bind a gmem
    range to multiple memslots, and fix the test that was supposed to ensure
    KVM returns -EEXIST.
 
  - Treat memslot binding offsets and sizes as unsigned values to fix a bug
    where KVM interprets a large "offset + size" as a negative value and allows
    a nonsensical offset.
 
  - Use the inode number instead of the page offset for the NUMA interleaving
    index to fix a bug where the effective index would jump by two for
    consecutive pages (the caller also adds in the page offset).
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEKTobbabEP7vbhhN9OlYIJqCjN/0FAmorOD8ACgkQOlYIJqCj
 N/1bTQ//XLFO1tv8sPyJBcQIOjx86V86XUV1i9SDHgzZ4uXBltJZf2AnhU6r62on
 wsG0k2P0VCx+6kTLf4r6j2VI4ZipZL7zlAxNDVvFjpyJGTk4giXOs6mqoMaULiJH
 d7uKz0Unzyz4Zaqk6t0IIC+N3nm9tzPyFHIQruUX8Oyfw4KJP0JyIKaOv+DqSHZ3
 KRgdZCkPREAcIgZn2GpuGja91RJYPaeuEuW5CFBysHBVxIUkgjqis3A3lL0oUXfR
 NsR5TthFMPxrP9a8TAkjqgGzHWHOARc0EdwRC/pufqA1wonbp7bi6vssMjkg/G1m
 28yUDDkQ4/C3/xLiDDwL3tujip97F0CrxTcuPtXYsfos4fm0tz4dmkUXuDZo9dVn
 Bn+r/oN+4Y3TFYvVJauuwTJGmEkkPHAVVUVNTjpuZ+lgg2kIfv+/hgSxR0K6x1lv
 zVtvV/NYiIiNlMlMNjz+XEvmLiaFIlDTmA75NhR3qF2rJqngv0qZv7wpoWqg0hLY
 bZZhWZXxlL5Hzs9J1ZXY7EPYUIpd/q5/m4fYF/tWCsKS+kki1z+HLgY9bY3tYsGP
 EDcwBjPnZ0OUm3GQOzzPVho3jlscCPMkYibzCTVHZpMZNwvW+XJ7wKMVgugEhu+C
 vlXAAuQ58McgnaCrt38yx9K555X5sMDzpvCVbwG5A+sou9LXWFA=
 =aXdD
 -----END PGP SIGNATURE-----

Merge tag 'kvm-x86-gmem-7.2' of https://github.com/kvm-x86/linux into HEAD

KVM guest_memfd changes for 7.2

 - Return -EEXIST instead of -EINVAL if userspace attempts to bind a gmem
   range to multiple memslots, and fix the test that was supposed to ensure
   KVM returns -EEXIST.

 - Treat memslot binding offsets and sizes as unsigned values to fix a bug
   where KVM interprets a large "offset + size" as a negative value and allows
   a nonsensical offset.

 - Use the inode number instead of the page offset for the NUMA interleaving
   index to fix a bug where the effective index would jump by two for
   consecutive pages (the caller also adds in the page offset).
This commit is contained in:
Paolo Bonzini 2026-06-12 10:08:52 +02:00
commit 06ba195f13
4 changed files with 63 additions and 15 deletions

View File

@ -345,6 +345,16 @@ static void test_invalid_punch_hole(int fd, size_t total_size)
}
}
static void test_invalid_binding(struct kvm_vm *vm, int fd, size_t size)
{
int r;
r = __vm_set_user_memory_region2(vm, 0, KVM_MEM_GUEST_MEMFD, 0, size, 0,
fd, ALIGN_DOWN(INT64_MAX, page_size));
TEST_ASSERT(r && errno == EINVAL,
"Memslot with out-of-range offset+size should fail");
}
static void test_create_guest_memfd_invalid_sizes(struct kvm_vm *vm,
u64 guest_memfd_flags)
{
@ -408,17 +418,26 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
}
}
#define __gmem_test(__test, __vm, __flags, __gmem_size) \
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
\
test_##__test(fd, __gmem_size); \
test_##__test(args); \
close(fd); \
} while (0)
#define __gmem_test(__test, __vm, __flags, __gmem_size) \
____gmem_test(__test, __vm, __flags, __gmem_size, fd, __gmem_size)
#define gmem_test(__test, __vm, __flags) \
__gmem_test(__test, __vm, __flags, page_size * 4)
#define __gmem_test_vm(__test, __vm, __flags, __gmem_size) \
____gmem_test(__test, __vm, __flags, __gmem_size, __vm, fd, __gmem_size)
#define gmem_test_vm(__test, __vm, __flags) \
__gmem_test_vm(__test, __vm, __flags, page_size * 4)
static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
{
test_create_guest_memfd_multiple(vm);
@ -447,6 +466,7 @@ static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
gmem_test(file_size, vm, flags);
gmem_test(fallocate, vm, flags);
gmem_test(invalid_punch_hole, vm, flags);
gmem_test_vm(invalid_binding, vm, flags);
}
static void test_guest_memfd(unsigned long vm_type)

View File

@ -510,7 +510,7 @@ static void test_add_overlapping_private_memory_regions(void)
vm = vm_create_barebones_type(KVM_X86_SW_PROTECTED_VM);
memfd = vm_create_guest_memfd(vm, MEM_REGION_SIZE * 4, 0);
memfd = vm_create_guest_memfd(vm, MEM_REGION_SIZE * 5, 0);
vm_set_user_memory_region2(vm, MEM_REGION_SLOT, KVM_MEM_GUEST_MEMFD,
MEM_REGION_GPA, MEM_REGION_SIZE * 2, 0, memfd, 0);
@ -526,12 +526,35 @@ static void test_add_overlapping_private_memory_regions(void)
vm_set_user_memory_region2(vm, MEM_REGION_SLOT, KVM_MEM_GUEST_MEMFD,
MEM_REGION_GPA, 0, NULL, -1, 0);
/* Overlap the front half of the other slot. */
/*
* Verify that overlap in the guest_memfd bindings (i.e. in guest_memfd
* file offsets), but _not_ in the GPA space, fails with -EEXIST.
*/
r = __vm_set_user_memory_region2(vm, MEM_REGION_SLOT, KVM_MEM_GUEST_MEMFD,
MEM_REGION_GPA,
MEM_REGION_SIZE * 2,
0, memfd, MEM_REGION_SIZE);
TEST_ASSERT(r == -1 && errno == EEXIST,
"Overlapping guest_memfd() bindings should fail with EEXIST");
/* And now the back half of the other slot's guest_memfd binding. */
r = __vm_set_user_memory_region2(vm, MEM_REGION_SLOT, KVM_MEM_GUEST_MEMFD,
MEM_REGION_GPA,
MEM_REGION_SIZE * 2,
0, memfd, MEM_REGION_SIZE * 3);
TEST_ASSERT(r == -1 && errno == EEXIST,
"Overlapping guest_memfd() bindings should fail with EEXIST");
/*
* Repeat the overlap tests, but this time with overlap in the memslots
* GPA space. Regardless of where there is overlap, KVM should return
* -EEXIST.
*/
r = __vm_set_user_memory_region2(vm, MEM_REGION_SLOT, KVM_MEM_GUEST_MEMFD,
MEM_REGION_GPA * 2 - MEM_REGION_SIZE,
MEM_REGION_SIZE * 2,
0, memfd, 0);
TEST_ASSERT(r == -1 && errno == EEXIST, "%s",
TEST_ASSERT(r == -1 && errno == EEXIST,
"Overlapping guest_memfd() bindings should fail with EEXIST");
/* And now the back half of the other slot. */
@ -539,7 +562,7 @@ static void test_add_overlapping_private_memory_regions(void)
MEM_REGION_GPA * 2 + MEM_REGION_SIZE,
MEM_REGION_SIZE * 2,
0, memfd, 0);
TEST_ASSERT(r == -1 && errno == EEXIST, "%s",
TEST_ASSERT(r == -1 && errno == EEXIST,
"Overlapping guest_memfd() bindings should fail with EEXIST");
close(memfd);

View File

@ -438,11 +438,12 @@ static int kvm_gmem_set_policy(struct vm_area_struct *vma, struct mempolicy *mpo
}
static struct mempolicy *kvm_gmem_get_policy(struct vm_area_struct *vma,
unsigned long addr, pgoff_t *pgoff)
unsigned long addr, pgoff_t *ilx)
{
pgoff_t pgoff = vma->vm_pgoff + ((addr - vma->vm_start) >> PAGE_SHIFT);
struct inode *inode = file_inode(vma->vm_file);
*pgoff = vma->vm_pgoff + ((addr - vma->vm_start) >> PAGE_SHIFT);
*ilx = inode->i_ino;
/*
* Return the memory policy for this index, or NULL if none is set.
@ -453,7 +454,7 @@ static struct mempolicy *kvm_gmem_get_policy(struct vm_area_struct *vma,
* can then replace NULL with the default memory policy instead of the
* current task's memory policy.
*/
return mpol_shared_policy_lookup(&GMEM_I(inode)->policy, *pgoff);
return mpol_shared_policy_lookup(&GMEM_I(inode)->policy, pgoff);
}
#endif /* CONFIG_NUMA */
@ -640,15 +641,16 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
}
int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
unsigned int fd, loff_t offset)
unsigned int fd, uoff_t offset)
{
loff_t size = slot->npages << PAGE_SHIFT;
uoff_t size = slot->npages << PAGE_SHIFT;
unsigned long start, end;
struct gmem_file *f;
struct inode *inode;
struct file *file;
int r = -EINVAL;
BUILD_BUG_ON(sizeof(gpa_t) != sizeof(offset));
BUILD_BUG_ON(sizeof(gfn_t) != sizeof(slot->gmem.pgoff));
file = fget(fd);
@ -664,8 +666,7 @@ int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
inode = file_inode(file);
if (offset < 0 || !PAGE_ALIGNED(offset) ||
offset + size > i_size_read(inode))
if (!PAGE_ALIGNED(offset) || offset + size > i_size_read(inode))
goto err;
filemap_invalidate_lock(inode->i_mapping);
@ -675,6 +676,7 @@ int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
if (!xa_empty(&f->bindings) &&
xa_find(&f->bindings, &start, end - 1, XA_PRESENT)) {
r = -EEXIST;
filemap_invalidate_unlock(inode->i_mapping);
goto err;
}

View File

@ -3,6 +3,9 @@
#ifndef __KVM_MM_H__
#define __KVM_MM_H__ 1
#include <linux/kvm.h>
#include <linux/kvm_types.h>
/*
* Architectures can choose whether to use an rwlock or spinlock
* for the mmu_lock. These macros, for use in common code
@ -72,7 +75,7 @@ int kvm_gmem_init(struct module *module);
void kvm_gmem_exit(void);
int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args);
int kvm_gmem_bind(struct kvm *kvm, struct kvm_memory_slot *slot,
unsigned int fd, loff_t offset);
unsigned int fd, uoff_t offset);
void kvm_gmem_unbind(struct kvm_memory_slot *slot);
#else
static inline int kvm_gmem_init(struct module *module)
@ -82,7 +85,7 @@ static inline int kvm_gmem_init(struct module *module)
static inline void kvm_gmem_exit(void) {};
static inline int kvm_gmem_bind(struct kvm *kvm,
struct kvm_memory_slot *slot,
unsigned int fd, loff_t offset)
unsigned int fd, uoff_t offset)
{
WARN_ON_ONCE(1);
return -EIO;