* [RFC PATCH v3 1/5] KVM: guest_memfd: Refactor out kvm_gmem_alloc_file()
2026-10-26 6:44 [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Tao Gong
@ 2026-10-26 6:44 ` Tao Gong
2026-10-26 6:44 ` [RFC PATCH v3 2/5] KVM: guest_memfd: Add ioctl KVM_LINK_GUEST_MEMFD Tao Gong
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Tao Gong @ 2026-10-26 6:44 UTC (permalink / raw)
To: kvm, Paolo Bonzini, Sean Christopherson
Cc: Ackerley Tng, David Hildenbrand, Ryan Afranji, Vishal Annapurve,
Sagi Shahar, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, linux-doc, linux-kernel, linux-kselftest,
Tao Gong
From: Ackerley Tng <ackerleytng@google.com>
The file represents a KVM instance's view into the underlying memory
that is managed by the guest_memfd inode. Refactoring out the file
creation enables this function to be reused when creating multiple views
into the same guest_memfd inode.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Ryan Afranji <afranji@google.com>
Signed-off-by: Ryan Afranji <afranji@google.com>
Co-developed-by: Tao Gong <gongtao@google.com>
Signed-off-by: Tao Gong <gongtao@google.com>
---
virt/kvm/guest_memfd.c | 67 +++++++++++++++++++++++++-----------------
1 file changed, 40 insertions(+), 27 deletions(-)
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 93f3d29d5751a..3ca6f64d48040 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -937,10 +937,47 @@ static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
return r;
}
+/*
+ * Allocate a new guest_memfd file for @kvm that refers to @inode. On success,
+ * the returned file takes over the caller's reference to @inode.
+ */
+static struct file *kvm_gmem_alloc_file(struct kvm *kvm, struct inode *inode,
+ const char *name)
+{
+ struct gmem_file *f;
+ struct file *file;
+
+ f = kzalloc_obj(*f);
+ if (!f)
+ return ERR_PTR(-ENOMEM);
+
+ /* __fput() will take care of fops_put(). */
+ if (!fops_get(&kvm_gmem_fops)) {
+ kfree(f);
+ return ERR_PTR(-ENOENT);
+ }
+
+ file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
+ if (IS_ERR(file)) {
+ fops_put(&kvm_gmem_fops);
+ kfree(f);
+ return file;
+ }
+
+ file->f_flags |= O_LARGEFILE;
+ file->private_data = f;
+
+ kvm_get_kvm(kvm);
+ f->kvm = kvm;
+ xa_init(&f->bindings);
+ list_add(&f->entry, &GMEM_I(inode)->gmem_file_list);
+
+ return file;
+}
+
static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
{
static const char *name = "[kvm-gmem]";
- struct gmem_file *f;
struct inode *inode;
struct file *file;
int fd, err;
@@ -949,51 +986,27 @@ static int __kvm_gmem_create(struct kvm *kvm, loff_t size, u64 flags)
if (fd < 0)
return fd;
- f = kzalloc_obj(*f);
- if (!f) {
- err = -ENOMEM;
- goto err_fd;
- }
-
- /* __fput() will take care of fops_put(). */
- if (!fops_get(&kvm_gmem_fops)) {
- err = -ENOENT;
- goto err_gmem;
- }
-
inode = anon_inode_make_secure_inode(kvm_gmem_mnt->mnt_sb, name, NULL);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
- goto err_fops;
+ goto err_fd;
}
err = kvm_gmem_init_inode(inode, size, flags);
if (err)
goto err_inode;
- file = alloc_file_pseudo(inode, kvm_gmem_mnt, name, O_RDWR, &kvm_gmem_fops);
+ file = kvm_gmem_alloc_file(kvm, inode, name);
if (IS_ERR(file)) {
err = PTR_ERR(file);
goto err_inode;
}
- file->f_flags |= O_LARGEFILE;
- file->private_data = f;
-
- kvm_get_kvm(kvm);
- f->kvm = kvm;
- xa_init(&f->bindings);
- list_add(&f->entry, &GMEM_I(inode)->gmem_file_list);
-
fd_install(fd, file);
return fd;
err_inode:
iput(inode);
-err_fops:
- fops_put(&kvm_gmem_fops);
-err_gmem:
- kfree(f);
err_fd:
put_unused_fd(fd);
return err;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v3 2/5] KVM: guest_memfd: Add ioctl KVM_LINK_GUEST_MEMFD
2026-10-26 6:44 [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Tao Gong
2026-10-26 6:44 ` [RFC PATCH v3 1/5] KVM: guest_memfd: Refactor out kvm_gmem_alloc_file() Tao Gong
@ 2026-10-26 6:44 ` Tao Gong
2026-10-26 7:19 ` sashiko-bot
2026-10-26 6:44 ` [RFC PATCH v3 3/5] KVM: guest_memfd: Advertise support for KVM_LINK_GUEST_MEMFD Tao Gong
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Tao Gong @ 2026-10-26 6:44 UTC (permalink / raw)
To: kvm, Paolo Bonzini, Sean Christopherson
Cc: Ackerley Tng, David Hildenbrand, Ryan Afranji, Vishal Annapurve,
Sagi Shahar, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, linux-doc, linux-kernel, linux-kselftest,
Tao Gong
From: Ackerley Tng <ackerleytng@google.com>
A guest_memfd file is bound to the KVM instance that created it and
cannot be bound to another VM's memslots. Add KVM_LINK_GUEST_MEMFD to
link an existing guest_memfd's underlying inode to a new file (and fd)
bound to a target VM, allowing multiple VMs on the same host to use the
same underlying guest_memfd memory without copying (e.g. for intra-host
migration).
Only allow linking between distinct, compatible VMs (same VM type on x86)
so that private memory of a confidential VM cannot be mapped into an
incompatible VM, and serialize additions to the inode's gmem_file_list
with the mapping's invalidate_lock.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Ryan Afranji <afranji@google.com>
Signed-off-by: Ryan Afranji <afranji@google.com>
Co-developed-by: Tao Gong <gongtao@google.com>
Signed-off-by: Tao Gong <gongtao@google.com>
---
Documentation/virt/kvm/api.rst | 42 +++++++++++++++++
arch/x86/kvm/x86.c | 10 ++++
include/linux/kvm_host.h | 1 +
include/uapi/linux/kvm.h | 8 ++++
virt/kvm/guest_memfd.c | 83 ++++++++++++++++++++++++++++++++++
virt/kvm/guest_memfd.h | 1 +
virt/kvm/kvm_main.c | 10 ++++
7 files changed, 155 insertions(+)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 3d6a4bf01c700..7c527a1e7c487 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6759,6 +6759,48 @@ if the guest_memfd memory was pinned in IOMMU page tables.
See also: :ref:`KVM_SET_MEMORY_ATTRIBUTES`.
+4.147 KVM_LINK_GUEST_MEMFD
+--------------------------
+
+:Capability: KVM_CAP_GUEST_MEMFD_LINK
+:Architectures: all
+:Type: vm ioctl
+:Parameters: struct kvm_link_guest_memfd (in)
+:Returns: A file descriptor on success, <0 on error
+
+Errors:
+
+ ========== ===============================================================
+ EINVAL ``fd`` is not an open guest_memfd file descriptor, ``flags`` or
+ ``reserved`` are not zero, the target VM is not compatible with
+ the VM ``fd`` is associated with (e.g. on x86, the VMs are of
+ different VM types), or ``fd`` is already associated with the
+ target VM.
+ EFAULT The parameter address was invalid.
+ ENOMEM Ran out of memory.
+ ========== ===============================================================
+
+::
+
+ struct kvm_link_guest_memfd {
+ __u64 fd;
+ __u64 flags;
+ __u64 reserved[6];
+ };
+
+KVM_LINK_GUEST_MEMFD creates a new guest_memfd file descriptor that is
+associated with the target VM (the VM on which the ioctl is invoked) and refers
+to the same underlying memory as the existing guest_memfd referred to by ``fd``,
+similar to how link() creates another name for an existing file. The new file
+descriptor can be used in KVM_SET_USER_MEMORY_REGION2 for the target VM, and
+the memory's contents, size, flags and (with in-place conversion) memory
+attributes are shared by all linked guest_memfd instances.
+
+The primary use case is intra-host migration, where the destination VM needs to
+access the same guest memory as the source VM without copying it.
+
+No flags are defined yet; ``flags`` and ``reserved`` must be zero.
+
.. _kvm_run:
5. The kvm_run structure
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c081e4b9b25de..4427d32290c0f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10648,6 +10648,16 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
return gmem_in_place_conversion || !kvm_arch_has_private_mem(kvm);
}
+bool kvm_arch_gmem_link_allowed(struct kvm *dst, struct kvm *src)
+{
+ /*
+ * Different VM types have different requirements for, and different
+ * (vendor) state associated with, guest_memfd memory, e.g. memory that
+ * is private to a TDX guest must never be mapped into a non-TDX VM.
+ */
+ return dst->arch.vm_type == src->arch.vm_type;
+}
+
#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CONVERT
int kvm_arch_gmem_make_private(struct kvm *kvm, gfn_t gfn, kvm_pfn_t pfn,
kvm_pfn_t nr_pages)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 485f18454eb45..5cffb7ad376e1 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2585,6 +2585,7 @@ static inline bool kvm_is_private_gfn(struct kvm *kvm, gfn_t gfn)
#ifdef CONFIG_KVM_GUEST_MEMFD
bool kvm_gmem_is_private_gfn(struct kvm *kvm, gfn_t gfn);
bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm);
+bool kvm_arch_gmem_link_allowed(struct kvm *dst, struct kvm *src);
static inline u64 kvm_gmem_get_supported_flags(struct kvm *kvm)
{
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 8dff2fc1972e9..53fe8dce57db2 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1690,4 +1690,12 @@ struct kvm_pre_fault_memory {
__u64 padding[5];
};
+#define KVM_LINK_GUEST_MEMFD _IOWR(KVMIO, 0xd6, struct kvm_link_guest_memfd)
+
+struct kvm_link_guest_memfd {
+ __u64 fd;
+ __u64 flags;
+ __u64 reserved[6];
+};
+
#endif /* __LINUX_KVM_H */
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 3ca6f64d48040..54b11747a8c48 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -892,6 +892,15 @@ bool __weak kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
return true;
}
+/*
+ * Returns true if a guest_memfd instance associated with @src may be linked
+ * into @dst, i.e. if both VMs can safely share the underlying memory.
+ */
+bool __weak kvm_arch_gmem_link_allowed(struct kvm *dst, struct kvm *src)
+{
+ return kvm_arch_has_private_mem(dst) == kvm_arch_has_private_mem(src);
+}
+
static int kvm_gmem_init_inode(struct inode *inode, loff_t size, u64 flags)
{
struct gmem_inode *gi = GMEM_I(inode);
@@ -970,7 +979,10 @@ static struct file *kvm_gmem_alloc_file(struct kvm *kvm, struct inode *inode,
kvm_get_kvm(kvm);
f->kvm = kvm;
xa_init(&f->bindings);
+
+ filemap_invalidate_lock(inode->i_mapping);
list_add(&f->entry, &GMEM_I(inode)->gmem_file_list);
+ filemap_invalidate_unlock(inode->i_mapping);
return file;
}
@@ -1026,6 +1038,77 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
return __kvm_gmem_create(kvm, size, flags);
}
+int kvm_gmem_link(struct kvm *kvm, struct kvm_link_guest_memfd *args)
+{
+ static const char *name = "[kvm-gmem]";
+ u64 flags = args->flags;
+ u64 valid_flags = 0;
+ struct file *dst_file, *src_file;
+ struct gmem_file *gmem;
+ struct timespec64 ts;
+ struct inode *inode;
+ struct fd f;
+ int ret, fd, i;
+
+ if (flags & ~valid_flags)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(args->reserved); i++) {
+ if (args->reserved[i])
+ return -EINVAL;
+ }
+
+ /* Don't let fdget() silently truncate the fd. */
+ if (args->fd > INT_MAX)
+ return -EINVAL;
+
+ f = fdget(args->fd);
+ src_file = fd_file(f);
+ if (!src_file)
+ return -EINVAL;
+
+ ret = -EINVAL;
+ if (src_file->f_op != &kvm_gmem_fops)
+ goto out;
+
+ /* Cannot link a gmem file with the same VM again. */
+ gmem = src_file->private_data;
+ if (gmem->kvm == kvm)
+ goto out;
+
+ if (!kvm_arch_gmem_link_allowed(kvm, gmem->kvm))
+ goto out;
+
+ fd = get_unused_fd_flags(0);
+ if (fd < 0) {
+ ret = fd;
+ goto out;
+ }
+
+ inode = file_inode(src_file);
+ ihold(inode);
+
+ dst_file = kvm_gmem_alloc_file(kvm, inode, name);
+ if (IS_ERR(dst_file)) {
+ iput(inode);
+ ret = PTR_ERR(dst_file);
+ goto out_fd;
+ }
+
+ ts = inode_set_ctime_current(inode);
+ inode_set_atime_to_ts(inode, ts);
+
+ fd_install(fd, dst_file);
+ fdput(f);
+ return fd;
+
+out_fd:
+ put_unused_fd(fd);
+out:
+ fdput(f);
+ return ret;
+}
+
int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,
unsigned int fd, uoff_t offset)
{
diff --git a/virt/kvm/guest_memfd.h b/virt/kvm/guest_memfd.h
index 01bd359d27e30..7ffa4fa7337e9 100644
--- a/virt/kvm/guest_memfd.h
+++ b/virt/kvm/guest_memfd.h
@@ -8,6 +8,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_link(struct kvm *kvm, struct kvm_link_guest_memfd *args);
int kvm_gmem_prepare_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot,
unsigned int fd, uoff_t offset);
int kvm_gmem_commit_memory_region(struct kvm *kvm, struct kvm_memory_slot *slot);
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 3ed481c4a15b6..70521371b64ab 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -5453,6 +5453,16 @@ static long kvm_vm_ioctl(struct file *filp,
r = kvm_gmem_create(kvm, &guest_memfd);
break;
}
+ case KVM_LINK_GUEST_MEMFD: {
+ struct kvm_link_guest_memfd params;
+
+ r = -EFAULT;
+ if (copy_from_user(¶ms, argp, sizeof(params)))
+ goto out;
+
+ r = kvm_gmem_link(kvm, ¶ms);
+ break;
+ }
#endif
default:
r = kvm_arch_vm_ioctl(filp, ioctl, arg);
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [RFC PATCH v3 2/5] KVM: guest_memfd: Add ioctl KVM_LINK_GUEST_MEMFD
2026-10-26 6:44 ` [RFC PATCH v3 2/5] KVM: guest_memfd: Add ioctl KVM_LINK_GUEST_MEMFD Tao Gong
@ 2026-10-26 7:19 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-26 7:19 UTC (permalink / raw)
To: Tao Gong, kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] KVM: guest_memfd: multiple same-VM links to one inode in kvm_gmem_link()
- [High] KVM: x86: kvm_arch_gmem_link_allowed() allows unsafe TDX and SEV guest_memfd linking
--
commit f69df48ff894e872fc6ccbcb9ce7d8cfde5a15f3
Author: Ackerley Tng <ackerleytng@google.com>
KVM: guest_memfd: Add ioctl KVM_LINK_GUEST_MEMFD
This commit introduces the KVM_LINK_GUEST_MEMFD ioctl to link an existing
guest_memfd's underlying inode to a new file descriptor bound to a target VM,
allowing compatible VMs to share the same underlying guest_memfd memory.
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index c081e4b9b25de..4427d32290c0f 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -10648,6 +10648,16 @@ bool kvm_arch_supports_gmem_init_shared(struct kvm *kvm)
> return gmem_in_place_conversion || !kvm_arch_has_private_mem(kvm);
> }
>
> +bool kvm_arch_gmem_link_allowed(struct kvm *dst, struct kvm *src)
> +{
> + /*
> + * Different VM types have different requirements for, and different
> + * (vendor) state associated with, guest_memfd memory, e.g. memory that
> + * is private to a TDX guest must never be mapped into a non-TDX VM.
> + */
> + return dst->arch.vm_type == src->arch.vm_type;
> +}
[Severity: High]
Is checking dst->arch.vm_type == src->arch.vm_type in
kvm_arch_gmem_link_allowed() sufficient for confidential VM types on x86?
On Intel TDX (KVM_X86_TDX_VM), each physical page in a guest_memfd is
exclusively owned in the TDX Module PAMT by a single TD's TDR and encrypted
with that TD's private HKID. If userspace links a guest_memfd between two TDX
VMs and both VMs populate or fault in the same private page via
tdx_mem_page_add() or tdx_mem_page_aug(), the second TD's SEAMCALL will fail
with TDX_PAGE_NOT_FREE and trigger a host WARN_ON_ONCE via TDX_BUG_ON_2():
arch/x86/kvm/vmx/tdx.c:tdx_mem_page_aug() {
err = tdh_mem_page_aug(&kvm_tdx->td, gpa, level, pfn, &entry, &level_state);
if (unlikely(tdx_operand_busy(err)))
return -EBUSY;
if (TDX_BUG_ON_2(err, TDH_MEM_PAGE_AUG, entry, level_state, kvm))
return -EIO;
}
On AMD SEV-SNP (KVM_X86_SNP_VM), each physical page's hardware RMP entry
records a single (ASID, GPA) owner. If two SNP VMs link the same guest_memfd:
1. When dst faults in a private page already assigned in the RMP to src,
sev_gmem_make_private() checks snp_lookup_rmpentry() without verifying the
owning ASID or GPA, and returns 0, installing an NPT mapping in dst for a
PFN owned by src's ASID:
arch/x86/kvm/svm/sev.c:sev_gmem_make_private() {
rc = snp_lookup_rmpentry(pfn, &assigned, &level);
...
if (assigned) {
pr_debug("%s: already assigned: gfn %llx pfn %llx nr_pages %llx level %d\n",
__func__, gfn, pfn, nr_pages, level);
return 0;
}
}
2. If src closes its guest_memfd and is destroyed while dst keeps the linked
guest_memfd open, the shared inode's folios are not freed, so
kvm_gmem_free_folio() -> sev_gmem_make_shared() does not run.
sev_vm_destroy() then decommissions src's SNP context and frees src's ASID
via sev_asid_free() while physical pages in the guest_memfd remain
Assigned=1 to that freed/recycled ASID in the RMP.
3. If dst converts pages to shared or punches a hole while src has pages
marked Immutable=1 in the RMP via KVM_SEV_SNP_LAUNCH_UPDATE,
sev_gmem_make_shared() -> rmp_make_shared() fails and triggers WARN_ONCE().
In addition, for legacy SEV and SEV-ES VMs initialized via KVM_SEV_INIT or
KVM_SEV_ES_INIT, sev_guest_init() leaves kvm->arch.vm_type set to
KVM_X86_DEFAULT_VM, so kvm_arch_gmem_link_allowed() treats legacy SEV/SEV-ES
VMs and non-SEV default VMs as having the same vm_type.
Should kvm_arch_gmem_link_allowed() disallow linking when either VM is a
confidential VM (or only allow KVM_X86_DEFAULT_VM without SEV enabled)?
> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
> index 3ca6f64d48040..54b11747a8c48 100644
> --- a/virt/kvm/guest_memfd.c
> +++ b/virt/kvm/guest_memfd.c
[ ... ]
> @@ -970,7 +979,10 @@ static struct file *kvm_gmem_alloc_file(struct kvm *kvm, struct inode *inode,
> kvm_get_kvm(kvm);
> f->kvm = kvm;
> xa_init(&f->bindings);
> +
> + filemap_invalidate_lock(inode->i_mapping);
> list_add(&f->entry, &GMEM_I(inode)->gmem_file_list);
> + filemap_invalidate_unlock(inode->i_mapping);
>
> return file;
> }
> @@ -1026,6 +1038,77 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)
> return __kvm_gmem_create(kvm, size, flags);
> }
>
> +int kvm_gmem_link(struct kvm *kvm, struct kvm_link_guest_memfd *args)
> +{
[ ... ]
> + ret = -EINVAL;
> + if (src_file->f_op != &kvm_gmem_fops)
> + goto out;
> +
> + /* Cannot link a gmem file with the same VM again. */
> + gmem = src_file->private_data;
> + if (gmem->kvm == kvm)
> + goto out;
> +
> + if (!kvm_arch_gmem_link_allowed(kvm, gmem->kvm))
> + goto out;
> +
> + fd = get_unused_fd_flags(0);
> + if (fd < 0) {
> + ret = fd;
> + goto out;
> + }
> +
> + inode = file_inode(src_file);
> + ihold(inode);
> +
> + dst_file = kvm_gmem_alloc_file(kvm, inode, name);
> + if (IS_ERR(dst_file)) {
> + iput(inode);
> + ret = PTR_ERR(dst_file);
> + goto out_fd;
> + }
[Severity: High]
Can kvm_gmem_link() still link the same guest_memfd inode to the same VM
multiple times?
In kvm_gmem_link(), the check only verifies whether src_file's immediate
gmem_file->kvm matches the target kvm, and does so without holding
filemap_invalidate_lock(inode->i_mapping).
If userspace creates fd1 on VM1 and links fd1 to VM2 (producing fd2), it can
then:
1. Call KVM_LINK_GUEST_MEMFD(fd1) on VM2 a second time,
2. Call KVM_LINK_GUEST_MEMFD(fd2) back onto VM1, or
3. Call KVM_LINK_GUEST_MEMFD(fd1) concurrently from two threads on VM2.
In all three cases, gmem->kvm == kvm is false, so kvm_gmem_alloc_file()
inserts a second struct gmem_file for the same struct kvm onto
GMEM_I(inode)->gmem_file_list.
Having multiple struct gmem_file instances for the same VM on the same inode
breaks two assumptions elsewhere in guest_memfd.c:
First, kvm_gmem_commit_memory_region() only checks f->bindings on the single
gmem_file being bound:
virt/kvm/guest_memfd.c:kvm_gmem_commit_memory_region() {
if (!xa_empty(&f->bindings) &&
xa_find(&f->bindings, &start, end - 1, XA_PRESENT)) {
filemap_invalidate_unlock(inode->i_mapping);
return -EEXIST;
}
}
With two gmem_file fds for the same VM and inode, a single VM can bind two
memslots at different GPAs to the exact same pgoff range of the same
guest_memfd inode.
Second, kvm_gmem_invalidate_start() iterates over gmem_file_list and calls
__kvm_gmem_invalidate_start() per gmem_file, which acquires and releases
KVM_MMU_LOCK(kvm) per gmem_file while mmu_invalidate_in_progress is active.
Should GMEM_I(inode)->gmem_file_list be walked while holding
filemap_invalidate_lock(inode->i_mapping) to reject any link where an entry
with f->kvm == kvm already exists on the inode?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026064424.3003536-1-gongtao@google.com?part=2
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC PATCH v3 3/5] KVM: guest_memfd: Advertise support for KVM_LINK_GUEST_MEMFD
2026-10-26 6:44 [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Tao Gong
2026-10-26 6:44 ` [RFC PATCH v3 1/5] KVM: guest_memfd: Refactor out kvm_gmem_alloc_file() Tao Gong
2026-10-26 6:44 ` [RFC PATCH v3 2/5] KVM: guest_memfd: Add ioctl KVM_LINK_GUEST_MEMFD Tao Gong
@ 2026-10-26 6:44 ` Tao Gong
2026-10-26 7:35 ` sashiko-bot
2026-10-26 6:44 ` [RFC PATCH v3 4/5] KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl Tao Gong
2026-10-26 6:44 ` [RFC PATCH v3 5/5] KVM: selftests: Test transferring private memory to another VM Tao Gong
4 siblings, 1 reply; 9+ messages in thread
From: Tao Gong @ 2026-10-26 6:44 UTC (permalink / raw)
To: kvm, Paolo Bonzini, Sean Christopherson
Cc: Vishal Annapurve, David Hildenbrand, Ackerley Tng, Ryan Afranji,
Sagi Shahar, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, linux-doc, linux-kernel, linux-kselftest,
Tao Gong
From: Vishal Annapurve <vannapurve@google.com>
Advertise KVM_CAP_GUEST_MEMFD_LINK via KVM_CHECK_EXTENSION so that
userspace VMMs and selftests can discover support for linking
guest_memfd instances across VMs via KVM_LINK_GUEST_MEMFD.
Signed-off-by: Vishal Annapurve <vannapurve@google.com>
Co-developed-by: Tao Gong <gongtao@google.com>
Signed-off-by: Tao Gong <gongtao@google.com>
---
Documentation/virt/kvm/api.rst | 8 ++++++++
include/uapi/linux/kvm.h | 1 +
virt/kvm/kvm_main.c | 2 ++
3 files changed, 11 insertions(+)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 7c527a1e7c487..bc54212e48c32 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -9738,6 +9738,14 @@ take care to differentiate between these cases.
The presence of this capability indicates that the nested KVM guest can
start in ESA mode.
+8.48 KVM_CAP_GUEST_MEMFD_LINK
+-----------------------------
+
+:Architectures: all
+
+The presence of this capability indicates that KVM supports linking an
+existing guest_memfd file to another VM via KVM_LINK_GUEST_MEMFD.
+
9. Known KVM API problems
=========================
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 53fe8dce57db2..09926fa85fe6c 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1000,6 +1000,7 @@ struct kvm_enable_cap {
#define KVM_CAP_PPC_COMPAT_CAPS 250
#define KVM_CAP_ARM_PMU_V3_STRICT 251
#define KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES 252
+#define KVM_CAP_GUEST_MEMFD_LINK 253
struct kvm_irq_routing_irqchip {
__u32 irqchip;
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 70521371b64ab..135e7bb7609a8 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -5008,6 +5008,8 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
return 0;
return KVM_MEMORY_ATTRIBUTE_PRIVATE;
+ case KVM_CAP_GUEST_MEMFD_LINK:
+ return 1;
#endif
default:
break;
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [RFC PATCH v3 3/5] KVM: guest_memfd: Advertise support for KVM_LINK_GUEST_MEMFD
2026-10-26 6:44 ` [RFC PATCH v3 3/5] KVM: guest_memfd: Advertise support for KVM_LINK_GUEST_MEMFD Tao Gong
@ 2026-10-26 7:35 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-26 7:35 UTC (permalink / raw)
To: Tao Gong, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] KVM: guest_memfd: disallow linking guest_memfd across TDX and SEV-SNP VMs
--
commit 22378b77744900b2602bc894dbec906d31acf8be
Author: Vishal Annapurve <vannapurve@google.com>
KVM: guest_memfd: Advertise support for KVM_LINK_GUEST_MEMFD
This commit introduces KVM_CAP_GUEST_MEMFD_LINK and advertises it via
KVM_CHECK_EXTENSION so userspace can discover support for linking
guest_memfd instances across VMs with KVM_LINK_GUEST_MEMFD.
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 70521371b64ab..135e7bb7609a8 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -5008,6 +5008,8 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
> return 0;
>
> return KVM_MEMORY_ATTRIBUTE_PRIVATE;
> + case KVM_CAP_GUEST_MEMFD_LINK:
> + return 1;
[Severity: High]
Should KVM_CAP_GUEST_MEMFD_LINK and kvm_arch_gmem_link_allowed() restrict
linking guest_memfd instances for confidential VM types such as TDX and
SEV-SNP?
Right now, kvm_vm_ioctl_check_extension_generic() unconditionally returns 1
for all VM instances, and on x86, kvm_gmem_link() ->
kvm_arch_gmem_link_allowed() only checks whether the two VMs have the same
vm_type:
arch/x86/kvm/x86.c:kvm_arch_gmem_link_allowed() {
return dst->arch.vm_type == src->arch.vm_type;
}
This allows userspace to link a guest_memfd between two KVM_X86_TDX_VM
instances, two KVM_X86_SNP_VM instances, or between a legacy SEV VM
(initialized via KVM_SEV_INIT on a KVM_X86_DEFAULT_VM without changing
kvm->arch.vm_type) and a default VM:
1. For KVM_X86_TDX_VM, private guest_memfd pages are encrypted with a
per-TD ephemeral HKID and tracked in the TDX Module PAMT as exclusively
owned by a single TD. If two TDX VMs link a guest_memfd and both map the
same private PFN via KVM_TDX_INIT_MEM_REGION -> tdx_mem_page_add() or a
stage-2 EPT fault -> tdx_mem_page_aug(), the second TD's SEAMCALL fails
with TDX_PAGE_NOT_FREE:
arch/x86/kvm/vmx/tdx.c:tdx_mem_page_aug() {
err = tdh_mem_page_aug(&kvm_tdx->td, gpa, level, pfn, &entry, &level_state);
if (unlikely(tdx_operand_busy(err)))
return -EBUSY;
if (TDX_BUG_ON_2(err, TDH_MEM_PAGE_AUG, entry, level_state, kvm))
return -EIO;
}
Because TDX_PAGE_NOT_FREE is not an operand-busy error, TDX_BUG_ON_2()
triggers a host WARN_ON_ONCE().
2. For KVM_X86_SNP_VM, each physical page has a single system-wide RMP
entry binding it to one (ASID, GPA) pair. When a second SNP VM faults in a
private guest_memfd page already assigned in the RMP to the first SNP VM,
sev_gmem_make_private() sees assigned == true from snp_lookup_rmpentry()
and returns 0 without checking whether the RMP entry belongs to the current
VM's ASID:
arch/x86/kvm/svm/sev.c:sev_gmem_make_private() {
if (assigned) {
pr_debug("%s: already assigned: gfn %llx pfn %llx nr_pages %llx level %d\n",
__func__, gfn, pfn, nr_pages, level);
return 0;
}
}
KVM then installs the page into the second SNP VM's NPT, leading to
repeated guest RMP #NPF faults; furthermore, if the first SNP VM is
destroyed while the second SNP VM keeps the linked guest_memfd alive,
sev_vm_destroy() frees the first VM's ASID via sev_asid_free() while the
shared guest_memfd pages remain assigned to that ASID in the RMP table.
Should KVM_CAP_GUEST_MEMFD_LINK and kvm_arch_gmem_link_allowed() disallow
linking for TDX and SEV/SNP VMs?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026064424.3003536-1-gongtao@google.com?part=3
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC PATCH v3 4/5] KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl
2026-10-26 6:44 [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Tao Gong
` (2 preceding siblings ...)
2026-10-26 6:44 ` [RFC PATCH v3 3/5] KVM: guest_memfd: Advertise support for KVM_LINK_GUEST_MEMFD Tao Gong
@ 2026-10-26 6:44 ` Tao Gong
2026-10-26 7:42 ` sashiko-bot
2026-10-26 6:44 ` [RFC PATCH v3 5/5] KVM: selftests: Test transferring private memory to another VM Tao Gong
4 siblings, 1 reply; 9+ messages in thread
From: Tao Gong @ 2026-10-26 6:44 UTC (permalink / raw)
To: kvm, Paolo Bonzini, Sean Christopherson
Cc: Ackerley Tng, David Hildenbrand, Ryan Afranji, Vishal Annapurve,
Sagi Shahar, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, linux-doc, linux-kernel, linux-kselftest,
Tao Gong
From: Ackerley Tng <ackerleytng@google.com>
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@google.com>
Co-developed-by: Ryan Afranji <afranji@google.com>
Signed-off-by: Ryan Afranji <afranji@google.com>
Co-developed-by: Tao Gong <gongtao@google.com>
Signed-off-by: Tao Gong <gongtao@google.com>
---
.../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, ¶ms);
+ 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, ¶ms);
+ 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, ¶ms);
+}
+
+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
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [RFC PATCH v3 4/5] KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl
2026-10-26 6:44 ` [RFC PATCH v3 4/5] KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl Tao Gong
@ 2026-10-26 7:42 ` sashiko-bot
0 siblings, 0 replies; 9+ messages in thread
From: sashiko-bot @ 2026-10-26 7:42 UTC (permalink / raw)
To: Tao Gong, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] KVM: selftests: test_link() hits vm->type assert in ____vm_create() on arm64
--
commit c8e0fca46d65aef6ffa609495f3f615b1c614301
Author: Ackerley Tng <ackerleytng@google.com>
KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl
This commit adds kselftest helpers and test cases in guest_memfd_test.c for
the KVM_LINK_GUEST_MEMFD ioctl, covering invalid inputs and valid linking
between VMs.
> 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);
[Severity: Medium]
Will passing vm->type to vm_create_barebones_type() here trigger an
assertion failure in ____vm_create() on arm64 when vm->pa_bits != 40?
On arm64, main() invokes test_guest_memfd(VM_TYPE_DEFAULT) with vm_type = 0,
and the initial vm_create_barebones_type(vm_type) call in test_guest_memfd()
reaches tools/testing/selftests/kvm/lib/kvm_util.c:____vm_create():
#ifdef __aarch64__
TEST_ASSERT(!vm->type, "ARM doesn't support test-provided types");
if (vm->pa_bits != 40)
vm->type = KVM_VM_TYPE_ARM_IPA_SIZE(vm->pa_bits);
#endif
When the selected default guest mode has a physical address width other than
40 bits (for example on hosts where guest_modes_append_default() selects a
48-bit or 52-bit IPA mode), ____vm_create() overwrites vm->type in place
with a non-zero KVM_VM_TYPE_ARM_IPA_SIZE(vm->pa_bits) value.
When __test_guest_memfd() then calls test_link(), passing that mutated
vm->type into vm_create_barebones_type(vm->type) forwards a non-zero
shape.type into ____vm_create(), tripping TEST_ASSERT(!vm->type, ...).
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026064424.3003536-1-gongtao@google.com?part=4
^ permalink raw reply [flat|nested] 9+ messages in thread
* [RFC PATCH v3 5/5] KVM: selftests: Test transferring private memory to another VM
2026-10-26 6:44 [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Tao Gong
` (3 preceding siblings ...)
2026-10-26 6:44 ` [RFC PATCH v3 4/5] KVM: selftests: Add tests for KVM_LINK_GUEST_MEMFD ioctl Tao Gong
@ 2026-10-26 6:44 ` Tao Gong
4 siblings, 0 replies; 9+ messages in thread
From: Tao Gong @ 2026-10-26 6:44 UTC (permalink / raw)
To: kvm, Paolo Bonzini, Sean Christopherson
Cc: Ackerley Tng, David Hildenbrand, Ryan Afranji, Vishal Annapurve,
Sagi Shahar, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, linux-doc, linux-kernel, linux-kselftest,
Tao Gong
From: Ackerley Tng <ackerleytng@google.com>
Add private_mem_migrate_tests to verify that private memory backed by
guest_memfd can be linked into another VM of the same type
(KVM_X86_SW_PROTECTED_VM) on the same host via KVM_LINK_GUEST_MEMFD,
and that data written by the source VM is visible to the destination VM.
Also verify that linking a guest_memfd into a VM of a different VM type
is rejected with EINVAL.
Signed-off-by: Ackerley Tng <ackerleytng@google.com>
Co-developed-by: Ryan Afranji <afranji@google.com>
Signed-off-by: Ryan Afranji <afranji@google.com>
Co-developed-by: Tao Gong <gongtao@google.com>
Signed-off-by: Tao Gong <gongtao@google.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
.../kvm/x86/private_mem_migrate_tests.c | 118 ++++++++++++++++++
2 files changed, 119 insertions(+)
create mode 100644 tools/testing/selftests/kvm/x86/private_mem_migrate_tests.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 239bc61ea384f..cffa0d165a945 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -108,6 +108,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_counters_test
TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
+TEST_GEN_PROGS_x86 += x86/private_mem_migrate_tests
TEST_GEN_PROGS_x86 += x86/save_restore_pf_stress_test
TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
TEST_GEN_PROGS_x86 += x86/set_sregs_test
diff --git a/tools/testing/selftests/kvm/x86/private_mem_migrate_tests.c b/tools/testing/selftests/kvm/x86/private_mem_migrate_tests.c
new file mode 100644
index 0000000000000..17bebf850b4c4
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/private_mem_migrate_tests.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "kvm_util.h"
+#include "test_util.h"
+#include "ucall_common.h"
+#include <linux/kvm.h>
+#include <linux/sizes.h>
+
+#define TRANSFER_PRIVATE_MEM_TEST_SLOT 10
+#define TRANSFER_PRIVATE_MEM_GPA ((uint64_t)(1ull << 32))
+#define TRANSFER_PRIVATE_MEM_GVA TRANSFER_PRIVATE_MEM_GPA
+#define TRANSFER_PRIVATE_MEM_VALUE 0xdeadbeef
+
+static const struct vm_shape protected_shape = {
+ .mode = VM_MODE_DEFAULT,
+ .type = KVM_X86_SW_PROTECTED_VM,
+};
+
+static void transfer_private_mem_guest_code_src(void)
+{
+ uint64_t *ptr = (uint64_t *)TRANSFER_PRIVATE_MEM_GVA;
+
+ WRITE_ONCE(*ptr, TRANSFER_PRIVATE_MEM_VALUE);
+
+ GUEST_SYNC1(READ_ONCE(*ptr));
+}
+
+static void transfer_private_mem_guest_code_dst(void)
+{
+ uint64_t *ptr = (uint64_t *)TRANSFER_PRIVATE_MEM_GVA;
+
+ GUEST_SYNC1(READ_ONCE(*ptr));
+}
+
+static void add_private_mem(struct kvm_vm *vm, int memfd)
+{
+ vm_mem_add(vm, DEFAULT_VM_MEM_SRC, TRANSFER_PRIVATE_MEM_GPA,
+ TRANSFER_PRIVATE_MEM_TEST_SLOT, 1, KVM_MEM_GUEST_MEMFD,
+ memfd, 0, 0);
+
+ virt_map(vm, TRANSFER_PRIVATE_MEM_GVA, TRANSFER_PRIVATE_MEM_GPA, 1);
+
+ /*
+ * guest_memfd memory is private by default if guest_memfd tracks
+ * memory attributes, otherwise it must be converted via the VM.
+ */
+ if (!kvm_has_gmem_attributes)
+ vm_mem_set_private(vm, TRANSFER_PRIVATE_MEM_GPA, SZ_4K);
+}
+
+static void test_transfer_private_mem(void)
+{
+ struct kvm_vm *src_vm, *dst_vm;
+ struct kvm_vcpu *src_vcpu, *dst_vcpu;
+ int src_memfd, dst_memfd;
+ struct ucall uc;
+
+ /* Build the source VM, use it to write to private memory */
+ src_vm = __vm_create_shape_with_one_vcpu(protected_shape, &src_vcpu, 0,
+ transfer_private_mem_guest_code_src);
+ src_memfd = vm_create_guest_memfd(src_vm, SZ_4K, 0);
+
+ add_private_mem(src_vm, src_memfd);
+
+ vcpu_run(src_vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(src_vcpu, KVM_EXIT_IO);
+ get_ucall(src_vcpu, &uc);
+ TEST_ASSERT(uc.args[0] == TRANSFER_PRIVATE_MEM_VALUE,
+ "Source VM should be able to write to private memory");
+
+ /* Build the destination VM with linked fd */
+ dst_vm = __vm_create_shape_with_one_vcpu(protected_shape, &dst_vcpu, 0,
+ transfer_private_mem_guest_code_dst);
+ dst_memfd = vm_link_guest_memfd(dst_vm, src_memfd, 0);
+
+ add_private_mem(dst_vm, dst_memfd);
+
+ vcpu_run(dst_vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(dst_vcpu, KVM_EXIT_IO);
+ get_ucall(dst_vcpu, &uc);
+ TEST_ASSERT(uc.args[0] == TRANSFER_PRIVATE_MEM_VALUE,
+ "Destination VM should be able to read value transferred");
+
+ close(dst_memfd);
+ close(src_memfd);
+ kvm_vm_free(dst_vm);
+ kvm_vm_free(src_vm);
+}
+
+static void test_link_different_vm_type(void)
+{
+ struct kvm_vm *src_vm, *dst_vm;
+ int src_memfd, ret;
+
+ src_vm = vm_create_barebones_type(KVM_X86_SW_PROTECTED_VM);
+ src_memfd = vm_create_guest_memfd(src_vm, SZ_4K, 0);
+
+ dst_vm = vm_create_barebones_type(KVM_X86_DEFAULT_VM);
+
+ ret = __vm_link_guest_memfd(dst_vm, src_memfd, 0);
+ TEST_ASSERT(ret == -1 && errno == EINVAL,
+ "Linking into a VM of a different type should fail, ret: %d, errno: %d",
+ ret, errno);
+
+ close(src_memfd);
+ kvm_vm_free(dst_vm);
+ kvm_vm_free(src_vm);
+}
+
+int main(int argc, char *argv[])
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM));
+ TEST_REQUIRE(kvm_has_cap(KVM_CAP_GUEST_MEMFD_LINK));
+
+ test_transfer_private_mem();
+ test_link_different_vm_type();
+
+ return 0;
+}
--
2.56.0.257.gf7497c7d27-goog
^ permalink raw reply related [flat|nested] 9+ messages in thread