[RFC PATCH v3 4/5] KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl

From: Tao Gong

Date: Mon Oct 26 2026 - 02:46:23 EST


From: Ackerley Tng <ackerleytng@xxxxxxxxxx>

Test that

+ Invalid inputs should be rejected with EINVAL
+ Successful inputs return a new (destination) fd
+ Destination and source fds have the same inode number
+ No crash on program exit

Signed-off-by: Ackerley Tng <ackerleytng@xxxxxxxxxx>
Co-developed-by: Ryan Afranji <afranji@xxxxxxxxxx>
Signed-off-by: Ryan Afranji <afranji@xxxxxxxxxx>
Co-developed-by: Tao Gong <gongtao@xxxxxxxxxx>
Signed-off-by: Tao Gong <gongtao@xxxxxxxxxx>
---
.../testing/selftests/kvm/guest_memfd_test.c | 64 +++++++++++++++++++
.../testing/selftests/kvm/include/kvm_util.h | 18 ++++++
2 files changed, 82 insertions(+)

diff --git a/tools/testing/selftests/kvm/guest_memfd_test.c b/tools/testing/selftests/kvm/guest_memfd_test.c
index 2233d871a38f4..ccde4d735c77c 100644
--- a/tools/testing/selftests/kvm/guest_memfd_test.c
+++ b/tools/testing/selftests/kvm/guest_memfd_test.c
@@ -418,6 +418,68 @@ static void test_guest_memfd_flags(struct kvm_vm *vm)
}
}

+static void test_link(struct kvm_vm *vm, int src_fd, size_t total_size)
+{
+ struct kvm_link_guest_memfd params = {};
+ struct stat src_stat, dst_stat;
+ struct kvm_vm *dst_vm;
+ int dst_fd, bad_fd;
+ int ret;
+
+ /* guest_memfd can only be linked between VMs of the same type. */
+ dst_vm = vm_create_barebones_type(vm->type);
+
+ /* Linking with an fd that isn't open */
+ bad_fd = dup(src_fd);
+ TEST_ASSERT(bad_fd >= 0, "dup() failed");
+ close(bad_fd);
+ ret = __vm_link_guest_memfd(dst_vm, bad_fd, 0);
+ TEST_ASSERT_EQ(ret, -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ /* Linking with an out-of-range fd, i.e. KVM must not truncate fd */
+ params.fd = (1ULL << 32) | src_fd;
+ ret = __vm_ioctl(dst_vm, KVM_LINK_GUEST_MEMFD, &params);
+ TEST_ASSERT_EQ(ret, -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ /* Linking with a non-gmem fd */
+ ret = __vm_link_guest_memfd(dst_vm, dst_vm->fd, 0);
+ TEST_ASSERT_EQ(ret, -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ /* Linking with invalid flags */
+ ret = __vm_link_guest_memfd(dst_vm, src_fd, 1);
+ TEST_ASSERT_EQ(ret, -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ /* Linking with non-zero reserved fields */
+ params.fd = src_fd;
+ params.reserved[5] = 1;
+ ret = __vm_ioctl(dst_vm, KVM_LINK_GUEST_MEMFD, &params);
+ TEST_ASSERT_EQ(ret, -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ /* Linking with an already-associated vm */
+ ret = __vm_link_guest_memfd(vm, src_fd, 0);
+ TEST_ASSERT_EQ(ret, -1);
+ TEST_ASSERT_EQ(errno, EINVAL);
+
+ dst_fd = __vm_link_guest_memfd(dst_vm, src_fd, 0);
+ TEST_ASSERT(dst_fd >= 0, "linking should succeed with valid inputs");
+ TEST_ASSERT(src_fd != dst_fd, "linking should return a different fd");
+
+ ret = fstat(src_fd, &src_stat);
+ TEST_ASSERT_EQ(ret, 0);
+ ret = fstat(dst_fd, &dst_stat);
+ TEST_ASSERT_EQ(ret, 0);
+ TEST_ASSERT(src_stat.st_ino == dst_stat.st_ino,
+ "src and dst files should have the same inode number");
+
+ close(dst_fd);
+ kvm_vm_free(dst_vm);
+}
+
#define ____gmem_test(__test, __vm, __flags, __gmem_size, args...) \
do { \
int fd = vm_create_guest_memfd(__vm, __gmem_size, __flags); \
@@ -467,6 +529,8 @@ static void __test_guest_memfd(struct kvm_vm *vm, u64 flags)
gmem_test(fallocate, vm, flags);
gmem_test(invalid_punch_hole, vm, flags);
gmem_test_vm(invalid_binding, vm, flags);
+ if (kvm_has_cap(KVM_CAP_GUEST_MEMFD_LINK))
+ gmem_test_vm(link, vm, flags);
}

static void test_guest_memfd(unsigned long vm_type)
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 777fa3dbf88d6..f8eca3f7993d0 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -787,6 +787,24 @@ static inline int vm_create_guest_memfd(struct kvm_vm *vm, u64 size,
return fd;
}

+static inline int __vm_link_guest_memfd(struct kvm_vm *vm, int fd, u64 flags)
+{
+ struct kvm_link_guest_memfd params = {
+ .fd = fd,
+ .flags = flags,
+ };
+
+ return __vm_ioctl(vm, KVM_LINK_GUEST_MEMFD, &params);
+}
+
+static inline int vm_link_guest_memfd(struct kvm_vm *vm, int fd, u64 flags)
+{
+ int new_fd = __vm_link_guest_memfd(vm, fd, flags);
+
+ TEST_ASSERT(new_fd >= 0, KVM_IOCTL_ERROR(KVM_LINK_GUEST_MEMFD, new_fd));
+ return new_fd;
+}
+
void vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
gpa_t gpa, u64 size, void *hva);
int __vm_set_user_memory_region(struct kvm_vm *vm, u32 slot, u32 flags,
--
2.56.0.257.gf7497c7d27-goog