* [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
` (4 subsequent siblings)
5 siblings, 0 replies; 10+ 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] 10+ 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
` (3 subsequent siblings)
5 siblings, 1 reply; 10+ 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] 10+ 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; 10+ 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] 10+ 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
` (2 subsequent siblings)
5 siblings, 1 reply; 10+ 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] 10+ 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; 10+ 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] 10+ 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
2026-10-29 20:01 ` [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Ackerley Tng
5 siblings, 1 reply; 10+ 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] 10+ 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; 10+ 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] 10+ 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
2026-10-29 20:01 ` [RFC PATCH v3 0/5] KVM: guest_memfd: Add KVM_LINK_GUEST_MEMFD ioctl Ackerley Tng
5 siblings, 0 replies; 10+ 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] 10+ messages in thread* Re: [RFC PATCH v3 0/5] KVM: guest_memfd: Add 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
` (4 preceding siblings ...)
2026-10-26 6:44 ` [RFC PATCH v3 5/5] KVM: selftests: Test transferring private memory to another VM Tao Gong
@ 2026-10-29 20:01 ` Ackerley Tng
5 siblings, 0 replies; 10+ messages in thread
From: Ackerley Tng @ 2026-10-29 20:01 UTC (permalink / raw)
To: Tao Gong, kvm, Paolo Bonzini, Sean Christopherson
Cc: 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, aneesh.kumar,
Fred Griffoul, David Woodhouse, Jacob Pan, John Hubbard,
pankaj.gupta, Alain Gefflaut (HE/HIM), michael.roth,
Samiullah Khawaja, suzuki.poulose
Tao Gong <gongtao@google.com> writes:
Adding a few more people to Cc after today's discussion at guest_memfd
biweekly.
Sean mentioned the link ioctl as the line of sight to sharing memory,
would like people's thoughts on this. Captured some of today's
discussion below.
> [...snip...]
>
> Use cases
> ---------
>
> 1. Intra-host migration of VMs with private memory, where the
> destination VM takes over the source VM's guest memory in place:
>
> a. Userspace passes the source VM's guest_memfd fd to the destination
> VM, e.g. over a Unix socket.
> b. The destination VM calls KVM_LINK_GUEST_MEMFD to get a new fd for
> the same inode, bound to the destination VM.
> c. The destination VM binds the new fd to its memslots with
> KVM_SET_USER_MEMORY_REGION2, at the same offsets as the source.
> d. The remaining VM state is moved with KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM
> (SEV only today; generalized by the follow-up series) and the
> destination VM is run as normal.
>
> Linking does not revoke the source VM's access; the source simply
> stops using the memory, so every private page keeps a single owner.
>
> 2. Sharing guest memory between non-confidential VMs. Both VMs bind the
> same inode at the same time, each through its own file, and the
> memory keeps its guest_memfd properties: neither VMM needs a
> userspace mapping of it, and inode-level operations such as a hole
> punch apply to every linked VM.
>
> A related use raised in discussion is Nitro Enclaves-style memory
> donation, where a guest gives part of its memory to a sibling VM on
> the same host ("nesting without real nesting").
>
We discussed Fred's "aliased" attribute that was going to be passed to
guest_memfd through .get_pfn(). "aliased" was going to indicate that
this memory is shared between two users of the same memory range.
> Alternatives considered
> -----------------------
>
> [...snip...]
>
> * Keeping the memory and its attributes in a "memory provider" and
> creating two independent guest_memfd instances on top of it. The
> guest_memfd inode already plays that role today (page cache,
> attributes and the list of files), so linking gets the same result
> without a new abstraction. If guest_memfd grows a provider layer,
> linking could be expressed on top of it.
>
Sean pointed out that we need to take into account that at some point
there CoCo protected memory might need sharing, where protected
indicates protected for a given guest (and let encryption be orthogonal
to protection).
Then, we'd need to know which struct kvm the memory belongs to, to
determine whether to share or not, and for that, guest_memfd would be
the natural choke point that understands memory, which kvm owns it, and
shared/private state, and hence memory sharing should be handled via
guest_memfd rather than directly through the provider.
>
>
> [...snip...]
>
^ permalink raw reply [flat|nested] 10+ messages in thread