1G page support for guest_memfd
Subsystems (22)
v2 cover 1G page support for guest_memfd
Hello,
This patchset builds upon discussion at LPC 2024 and many guest_memfd upstream calls to provide 1G page support for guest_memfd by taking pages from HugeTLB.
This patchset is based on Linux v6.15-rc6, and requires the mmap support for guest_memfd patchset (Thanks Fuad!) [1].
For ease of testing, this series is also available, stitched together, at https://github.com/googleprodkernel/linux-cc/tree/gmem-1g-page-support-rfc-v2
This patchset can be divided into two sections:
(a) Patches from the beginning up to and including "KVM: selftests:
Update script to map shared memory from guest_memfd" are a modified version of "conversion support for guest_memfd", which Fuad is managing [2].
(b) Patches after "KVM: selftests: Update script to map shared memory from guest_memfd" till the end are patches that actually bring in 1G page support for guest_memfd.
These are the significant differences between (a) and [2]:
+ [2] uses an xarray to track sharability, but I used a maple tree because for 1G pages, iterating pagewise to update shareability was prohibitively slow even for testing. I was choosing from among multi-index xarrays, interval trees and maple trees [3], and picked maple trees because
+ Maple trees were easier to figure out since I didn't have to compute the correct multi-index order and handle edge cases if the converted range wasn't a neat power of 2.
+ Maple trees were easier to figure out as compared to updating parts of a multi-index xarray.
+ Maple trees had an easier API to use than interval trees.
+ [2] doesn't yet have a conversion ioctl, but I needed it to test 1G support end-to-end.
+ (a) Removes guest_memfd from participating in LRU, which I needed, to get conversion selftests to work as expected, since participation in LRU was causing some unexpected refcounts on folios which was blocking conversions.
I am sending (a) in emails as well, as opposed to just leaving it on GitHub, so that we can discuss by commenting inline on emails. If you'd like to just look at 1G page support, here are some key takeaways from the first section (a):
+ If GUEST_MEMFD_FLAG_SUPPORT_SHARED is requested during guest_memfd creation, guest_memfd will
+ Track shareability (whether an index in the inode is guest-only or if the host is allowed to fault memory at a given index).
+ Always be used for guest faults - specifically, kvm_gmem_get_pfn() will be used to provide pages for the guest.
+ Always be used by KVM to check private/shared status of a gfn.
+ guest_memfd now has conversion ioctls, allowing conversion to private/shared
+ Conversion can fail if there are unexpected refcounts on any folios in the range.
Focusing on (b) 1G page support, here's an overview:
1. A bunch of refactoring patches for HugeTLB that isolates the allocation of a HugeTLB folio from other HugeTLB concepts such as VMA-level reservations, and HugeTLBfs-specific concepts, such as where memory policy is stored in the VMA, or where the subpool is stored on the inode.
2. A few patches that add a guestmem_hugetlb allocator within mm/. The guestmem_hugetlb allocator is a wrapper around HugeTLB to modularize the memory management functions, and to cleanly handle cleanup, so that folio cleanup can happen after the guest_memfd inode (and even KVM) goes away.
3. Some updates to guest_memfd to use the guestmem_hugetlb allocator.
4. Selftests for 1G page support.
Here are some remaining issues/TODOs:
1. Memory error handling such as machine check errors have not been implemented.
2. I've not looked into preparedness of pages, only zeroing has been considered.
3. When allocating HugeTLB pages, if two threads allocate indices mapping to the same huge page, the utilization in guest_memfd inode's subpool may momentarily go over the subpool limit (the requested size of the inode at guest_memfd creation time), causing one of the two threads to get -ENOMEM. Suggestions to solve this are appreciated!
4. max_usage_in_bytes statistic (cgroups v1) for guest_memfd HugeTLB pages should be correct but needs testing and could be wrong.
5. memcg charging (charge_memcg()) for cgroups v2 for guest_memfd HugeTLB pages after splitting should be correct but needs testing and could be wrong.
6. Page cache accounting: When a hugetlb page is split, guest_memfd will incur page count in both NR_HUGETLB (counted at hugetlb allocation time) and NR_FILE_PAGES stats (counted when split pages are added to the filemap). Is this aligned with what people expect?
Here are some optimizations that could be explored in future series:
1. Pages could be split from 1G to 2M first and only split to 4K if necessary.
2. Zeroing could be skipped for Coco VMs if hardware already zeroes the pages.
Here's RFC v1 [4] if you're interested in the motivation behind choosing HugeTLB, or the history of this patch series.
[1] https://lore.kernel.org/all/[email protected]/T/
[2] https://lore.kernel.org/all/[email protected]/T/
[3] https://lore.kernel.org/all/[email protected]/
[4] https://lore.kernel.org/all/[email protected]/T/
---
Ackerley Tng (49):
KVM: guest_memfd: Make guest mem use guest mem inodes instead of
anonymous inodes
KVM: guest_memfd: Introduce and use shareability to guard faulting
KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM: guest_memfd: Skip LRU for guest_memfd folios
KVM: Query guest_memfd for private/shared status
KVM: guest_memfd: Add CAP KVM_CAP_GMEM_CONVERSION
KVM: selftests: Test flag validity after guest_memfd supports
conversions
KVM: selftests: Test faulting with respect to
GUEST_MEMFD_FLAG_INIT_PRIVATE
KVM: selftests: Refactor vm_mem_add to be more flexible
KVM: selftests: Allow cleanup of ucall_pool from host
KVM: selftests: Test conversion flows for guest_memfd
KVM: selftests: Add script to exercise private_mem_conversions_test
KVM: selftests: Update private_mem_conversions_test to mmap
guest_memfd
KVM: selftests: Update script to map shared memory from guest_memfd
mm: hugetlb: Consolidate interpretation of gbl_chg within
alloc_hugetlb_folio()
mm: hugetlb: Cleanup interpretation of gbl_chg in
alloc_hugetlb_folio()
mm: hugetlb: Cleanup interpretation of map_chg_state within
alloc_hugetlb_folio()
mm: hugetlb: Rename alloc_surplus_hugetlb_folio
mm: mempolicy: Refactor out policy_node_nodemask()
mm: hugetlb: Inline huge_node() into callers
mm: hugetlb: Refactor hugetlb allocation functions
mm: hugetlb: Refactor out hugetlb_alloc_folio()
mm: hugetlb: Add option to create new subpool without using surplus
mm: truncate: Expose preparation steps for truncate_inode_pages_final
mm: hugetlb: Expose hugetlb_subpool_{get,put}_pages()
mm: Introduce guestmem_hugetlb to support folio_put() handling of
guestmem pages
mm: guestmem_hugetlb: Wrap HugeTLB as an allocator for guest_memfd
mm: truncate: Expose truncate_inode_folio()
KVM: x86: Set disallow_lpage on base_gfn and guest_memfd pgoff
misalignment
KVM: guest_memfd: Support guestmem_hugetlb as custom allocator
KVM: guest_memfd: Allocate and truncate from custom allocator
mm: hugetlb: Add functions to add/delete folio from hugetlb lists
mm: guestmem_hugetlb: Add support for splitting and merging pages
mm: Convert split_folio() macro to function
KVM: guest_memfd: Split allocator pages for guest_memfd use
KVM: guest_memfd: Merge and truncate on fallocate(PUNCH_HOLE)
KVM: guest_memfd: Update kvm_gmem_mapping_order to account for page
status
KVM: Add CAP to indicate support for HugeTLB as custom allocator
KVM: selftests: Add basic selftests for hugetlb-backed guest_memfd
KVM: selftests: Update conversion flows test for HugeTLB
KVM: selftests: Test truncation paths of guest_memfd
KVM: selftests: Test allocation and conversion of subfolios
KVM: selftests: Test that guest_memfd usage is reported via hugetlb
KVM: selftests: Support various types of backing sources for private
memory
KVM: selftests: Update test for various private memory backing source
types
KVM: selftests: Update private_mem_conversions_test.sh to test with
HugeTLB pages
KVM: selftests: Add script to test HugeTLB statistics
KVM: selftests: Test guest_memfd for accuracy of st_blocks
Elliot Berman (1):
filemap: Pass address_space mapping to ->free_folio()
Fuad Tabba (1):
mm: Consolidate freeing of typed folios on final folio_put()
Documentation/filesystems/locking.rst | 2 +- Documentation/filesystems/vfs.rst | 15 +- Documentation/virt/kvm/api.rst | 5 + arch/arm64/include/asm/kvm_host.h | 5 - arch/x86/include/asm/kvm_host.h | 10 - arch/x86/kvm/x86.c | 53 +- fs/hugetlbfs/inode.c | 2 +- fs/nfs/dir.c | 9 +- fs/orangefs/inode.c | 3 +- include/linux/fs.h | 2 +- include/linux/guestmem.h | 23 + include/linux/huge_mm.h | 6 +- include/linux/hugetlb.h | 19 +- include/linux/kvm_host.h | 32 +- include/linux/mempolicy.h | 11 +- include/linux/mm.h | 2 + include/linux/page-flags.h | 32 + include/uapi/linux/guestmem.h | 29 + include/uapi/linux/kvm.h | 16 + include/uapi/linux/magic.h | 1 + mm/Kconfig | 13 + mm/Makefile | 1 + mm/debug.c | 1 + mm/filemap.c | 12 +- mm/guestmem_hugetlb.c | 512 +++++ mm/guestmem_hugetlb.h | 9 + mm/hugetlb.c | 488 ++--- mm/internal.h | 1 - mm/memcontrol.c | 2 + mm/memory.c | 1 + mm/mempolicy.c | 44 +- mm/secretmem.c | 3 +- mm/swap.c | 32 +- mm/truncate.c | 27 +- mm/vmscan.c | 4 +- tools/testing/selftests/kvm/Makefile.kvm | 2 + .../kvm/guest_memfd_conversions_test.c | 797 ++++++++ .../kvm/guest_memfd_hugetlb_reporting_test.c | 384 ++++ ...uest_memfd_provide_hugetlb_cgroup_mount.sh | 36 + .../testing/selftests/kvm/guest_memfd_test.c | 293 ++- ...memfd_wrap_test_check_hugetlb_reporting.sh | 95 + .../testing/selftests/kvm/include/kvm_util.h | 104 +- .../testing/selftests/kvm/include/test_util.h | 20 +- .../selftests/kvm/include/ucall_common.h | 1 + tools/testing/selftests/kvm/lib/kvm_util.c | 465 +++-- tools/testing/selftests/kvm/lib/test_util.c | 102 + .../testing/selftests/kvm/lib/ucall_common.c | 16 +- .../kvm/x86/private_mem_conversions_test.c | 195 +- .../kvm/x86/private_mem_conversions_test.sh | 100 + virt/kvm/Kconfig | 5 + virt/kvm/guest_memfd.c | 1655 ++++++++++++++++- virt/kvm/kvm_main.c | 14 +- virt/kvm/kvm_mm.h | 9 +- 53 files changed, 5080 insertions(+), 640 deletions(-) create mode 100644 include/linux/guestmem.h create mode 100644 include/uapi/linux/guestmem.h create mode 100644 mm/guestmem_hugetlb.c create mode 100644 mm/guestmem_hugetlb.h create mode 100644 tools/testing/selftests/kvm/guest_memfd_conversions_test.c create mode 100644 tools/testing/selftests/kvm/guest_memfd_hugetlb_reporting_test.c create mode 100755 tools/testing/selftests/kvm/guest_memfd_provide_hugetlb_cgroup_mount.sh create mode 100755 tools/testing/selftests/kvm/guest_memfd_wrap_test_check_hugetlb_reporting.sh create mode 100755 tools/testing/selftests/kvm/x86/private_mem_conversions_test.sh
--
2.49.0.1045.g170613ef41-goog
RFC PATCH v2 1/51 KVM: guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodes
guest_memfd's inode represents memory the guest_memfd is providing. guest_memfd's file represents a struct kvm's view of that memory.
Using a custom inode allows customization of the inode teardown process via callbacks. For example, ->evict_inode() allows customization of the truncation process on file close, and ->destroy_inode() and ->free_inode() allow customization of the inode freeing process.
Customizing the truncation process allows flexibility in management of guest_memfd memory and customization of the inode freeing process allows proper cleanup of memory metadata stored on the inode.
Memory metadata is more appropriately stored on the inode (as opposed to the file), since the metadata is for the memory and is not unique to a specific binding and struct kvm.
Change-Id: I5c23bce8fefe492b40b8042ece1e81448752da99
--- include/uapi/linux/magic.h | 1 + virt/kvm/guest_memfd.c | 134 +++++++++++++++++++++++++++++++------ virt/kvm/kvm_main.c | 7 +- virt/kvm/kvm_mm.h | 9 ++- 4 files changed, 125 insertions(+), 26 deletions(-)
include/uapi/linux/magic.h +1 -0
@@ -103,5 +103,6 @@#define DEVMEM_MAGIC 0x454d444d/* "DMEM" */ #define SECRETMEM_MAGIC 0x5345434d/* "SECM" */ #define PID_FS_MAGIC 0x50494446/* "PIDF" */ +#define GUEST_MEMFD_MAGIC 0x474d454d/* "GMEM" */ #endif /* __LINUX_MAGIC_H__ */
virt/kvm/guest_memfd.c +112 -22
176 lines not on this page · raw message
virt/kvm/kvm_main.c +6 -1
@@ -6419,7 +6419,9 @@ int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module)if (WARN_ON_ONCE (r)) goto err_vfio; -kvm_gmem_init (module); + r =kvm_gmem_init (module); +if (r) + goto err_gmem; r =kvm_init_virtualization ();if (r)
@@ -6440,6 +6442,8 @@ int kvm_init(unsigned vcpu_size, unsigned vcpu_align, struct module *module) err_register:kvm_uninit_virtualization (); err_virt: +kvm_gmem_exit (); +err_gmem:kvm_vfio_ops_exit (); err_vfio:kvm_async_pf_deinit ();
@@ -6471,6 +6475,7 @@ void kvm_exit(void)for_each_possible_cpu (cpu)free_cpumask_var (per_cpu (cpu_kick_mask, cpu));kmem_cache_destroy (kvm_vcpu_cache); +kvm_gmem_exit ();kvm_vfio_ops_exit ();kvm_async_pf_deinit ();kvm_irqfd_exit ();
virt/kvm/kvm_mm.h +6 -3
@@ -68,17 +68,20 @@ static inline void gfn_to_pfn_cache_invalidate_start(struct kvm *kvm,#endif /* HAVE_KVM_PFNCACHE */ #ifdef CONFIG_KVM_GMEM -void kvm_gmem_init (struct module *module); +int kvm_gmem_init (struct module *module); +void kvm_gmem_exit (void );int kvm_gmem_create (struct kvm *kvm,struct kvm_create_guest_memfd *args);int kvm_gmem_bind (struct kvm *kvm,struct kvm_memory_slot *slot,unsigned int fd,loff_t offset);void kvm_gmem_unbind (struct kvm_memory_slot *slot);#else -static inline void kvm_gmem_init (struct module *module) +static inline int kvm_gmem_init (struct module *module) { - +return 0 ; } +static inline void kvm_gmem_exit (void ) {}; +static inline int kvm_gmem_bind (struct kvm *kvm,struct kvm_memory_slot *slot,unsigned int fd,loff_t offset)
Signature · 1 line
2.49.0.1045.g170613ef41-goog
RFC PATCH v2 2/51 KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngTrack guest_memfd memory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.
Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within guest_memfd instead of within KVM, like in kvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATE in kvm->mem_attr_array must still be retained to allow VMs to only use guest_memfd for private memory and some other memory for shared memory.
Not all use cases require guest_memfd() to be shared with the host when first created. Add a new flag, GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set on KVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.
--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)
Documentation/virt/kvm/api.rst +5 -0
@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM. +If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. + See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORY
include/uapi/linux/kvm.h +2 -0
@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;
virt/kvm/guest_memfd.c +122 -2
193 lines not on this page · raw message
Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 118 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +5
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM. +If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. + See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +34
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup (struct kvm_gmem_inode_private *private, +loff_t size,u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL ); +} +
virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 75
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." );
I noticed that in [1], the kvm_gmem_mmap() does not check the range. So, the WARN() here can be hit when userspace mmap() an area larger than the inode size and accesses the out of band HVA.
Maybe limit the mmap() range?
@@ -1609,6 +1620,10 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma)
if (!kvm_gmem_supports_shared(file_inode(file)))
return -ENODEV;+ if (vma->vm_end - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT) > i_size_read(file_inode(file)))
+ return -EINVAL;
+
if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) !=
(VM_SHARED | VM_MAYSHARE)) {
return -EINVAL;Quoted 80 lines
virt/kvm/guest_memfd.c +54
-1+return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err;
virt/kvm/guest_memfd.c static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 10 lines
virt/kvm/guest_memfd.c +6
-0inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability);
Wrap the mt_init() inside "#ifdef CONFIG_KVM_GMEM_SHARED_MEM" ?
Quoted 54 lines
virt/kvm/guest_memfd.c +17
-1+ inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); + folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; }Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 146 lines · Yan Zhao
Quoted 118 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +5
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM. +If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. + See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +34
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup (struct kvm_gmem_inode_private *private, +loff_t size,u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL ); +} +virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 75
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." );I noticed that in [1], the
kvm_gmem_mmap()does not check the range. So, theWARN()here can be hit when userspacemmap()an area larger than the inode size and accesses the out of band HVA.Maybe limit the
mmap()range?(quoted diff) +3
-0@@ -1609,6 +1620,10 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma) if (!kvm_gmem_supports_shared(file_inode(file))) return -ENODEV; + if (vma->vm_end - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT) > i_size_read(file_inode(file))) + return -EINVAL; + if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) != (VM_SHARED | VM_MAYSHARE)) { return -EINVAL;
Quoted 2 lines
This is a good idea. Thanks! I also think it is a good idea to include this with the guest_memfd mmap base series that Fuad is working on [1], maybe in v11.
[1] https://lore.kernel.org/all/[email protected]/
Quoted 84 lines
Quoted 84 lines
virt/kvm/guest_memfd.c +54
-1+return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode;
Quoted 8 lines
virt/kvm/guest_memfd.c static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 6 lines
virt/kvm/guest_memfd.c +6
-0+ err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability);Wrap the
mt_init()inside "#ifdefCONFIG_KVM_GMEM_SHARED_MEM" ?
Will fix this in the next revision. Will also update this to only initialize shareability if (flags & GUEST_MEMFD_FLAG_SUPPORT_SHARED).
Quoted 54 lines
Quoted 54 lines
virt/kvm/guest_memfd.c +17
-1+ inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); + folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; }Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Yan ZhaoHi,
.. snip..
Quoted 17 lines
I noticed that in [1], the
kvm_gmem_mmap()does not check the range. So, theWARN()here can be hit when userspacemmap()an area larger than the inode size and accesses the out of band HVA.Maybe limit the
mmap()range?(quoted diff) +3
-0@@ -1609,6 +1620,10 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma) if (!kvm_gmem_supports_shared(file_inode(file))) return -ENODEV; + if (vma->vm_end - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT) > i_size_read(file_inode(file))) + return -EINVAL; + if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) != (VM_SHARED | VM_MAYSHARE)) { return -EINVAL;
Quoted 1 line
I don't think we want to do that for a couple of reasons. We catch such invalid accesses on faulting, and, by analogy, afaikt, neither secretmem nor memfd perform a similar check on mmap (nor do memory-mapped files in general).
There are also valid reasons why a user would want to deliberately mmap more memory than the backing store, knowing that it's only going to fault what it's going to use, e.g., alignment.
Cheers,
/fuad
Quoted 146 lines
Quoted 80 lines
virt/kvm/guest_memfd.c +54
-1+return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err;virt/kvm/guest_memfd.c static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 10 lines
virt/kvm/guest_memfd.c +6
-0inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability);Wrap the
mt_init()inside "#ifdefCONFIG_KVM_GMEM_SHARED_MEM" ?Quoted 54 lines
virt/kvm/guest_memfd.c +17
-2+ inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); + folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; } --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 28 lines · Fuad Tabba
Hi,
.. snip..
Quoted 17 lines
I noticed that in [1], the
kvm_gmem_mmap()does not check the range. So, theWARN()here can be hit when userspacemmap()an area larger than the inode size and accesses the out of band HVA.Maybe limit the
mmap()range?(quoted diff) +3
-0@@ -1609,6 +1620,10 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma) if (!kvm_gmem_supports_shared(file_inode(file))) return -ENODEV; + if (vma->vm_end - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT) > i_size_read(file_inode(file))) + return -EINVAL; + if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) != (VM_SHARED | VM_MAYSHARE)) { return -EINVAL;Quoted 1 line
I don't think we want to do that for a couple of reasons. We catch such invalid accesses on faulting, and, by analogy, afaikt, neither secretmem nor memfd perform a similar check on mmap (nor do memory-mapped files in general).
Quoted 4 lines
There are also valid reasons why a user would want to deliberately mmap more memory than the backing store, knowing that it's only going to fault what it's going to use, e.g., alignment.
This is a good point.
I think there's no check against the inode size on faulting now though? v10's [1] kvm_gmem_fault_shared() calls kvm_gmem_get_folio() straightaway.
We should add a check like [2] to kvm_gmem_fault_shared().
[1] https://lore.kernel.org/all/[email protected]/
[2] https://github.com/torvalds/linux/blob/8477ab143069c6b05d6da4a8184ded8b969240f5/mm/filemap.c#L3373
Quoted 150 lines
Cheers,
/fuadQuoted 146 lines
Quoted 80 lines
virt/kvm/guest_memfd.c +54
-1+return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err;virt/kvm/guest_memfd.c static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 10 lines
virt/kvm/guest_memfd.c +6
-0inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability);Wrap the
mt_init()inside "#ifdefCONFIG_KVM_GMEM_SHARED_MEM" ?Quoted 54 lines
virt/kvm/guest_memfd.c +17
-2+ inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); + folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; } --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Hi Ackerley,
Quoted 36 lines · Ackerley Tng
Quoted 28 lines · Fuad Tabba
Hi,
.. snip..
Quoted 17 lines
I noticed that in [1], the
kvm_gmem_mmap()does not check the range. So, theWARN()here can be hit when userspacemmap()an area larger than the inode size and accesses the out of band HVA.Maybe limit the
mmap()range?(quoted diff) +3
-0@@ -1609,6 +1620,10 @@ static int kvm_gmem_mmap(struct file *file, struct vm_area_struct *vma) if (!kvm_gmem_supports_shared(file_inode(file))) return -ENODEV; + if (vma->vm_end - vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT) > i_size_read(file_inode(file))) + return -EINVAL; + if ((vma->vm_flags & (VM_SHARED | VM_MAYSHARE)) != (VM_SHARED | VM_MAYSHARE)) { return -EINVAL;Quoted 1 line
I don't think we want to do that for a couple of reasons. We catch such invalid accesses on faulting, and, by analogy, afaikt, neither secretmem nor memfd perform a similar check on mmap (nor do memory-mapped files in general).
Quoted 4 lines
There are also valid reasons why a user would want to deliberately mmap more memory than the backing store, knowing that it's only going to fault what it's going to use, e.g., alignment.
Quoted 7 lines
This is a good point.
I think there's no check against the inode size on faulting now though? v10's [1]
kvm_gmem_fault_shared()callskvm_gmem_get_folio()straightaway.We should add a check like [2] to
kvm_gmem_fault_shared().
Yes! I mistakenly thought that kvm_gmem_get_folio() had such a check, I just verified that it doesn't. I have added the check, as well as a new selftest to make sure we don't miss it in the future.
Thanks!
/fuad
Quoted 153 lines
[1] https://lore.kernel.org/all/[email protected]/
[2] https://github.com/torvalds/linux/blob/8477ab143069c6b05d6da4a8184ded8b969240f5/mm/filemap.c#L3373Quoted 150 lines
Cheers,
/fuadQuoted 146 lines
Quoted 80 lines
virt/kvm/guest_memfd.c +54
-1+return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err;virt/kvm/guest_memfd.c static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 10 lines
virt/kvm/guest_memfd.c +6
-0inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability);Wrap the
mt_init()inside "#ifdefCONFIG_KVM_GMEM_SHARED_MEM" ?Quoted 54 lines
virt/kvm/guest_memfd.c +17
-2+ inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); + folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; } --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngQuoted 37 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6412
Quoted 5 lines
Documentation/virt/kvm/api.rst +2
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM. +If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE.
It seems that the sentence is stale?
Didn't find the definition of KVM_CAP_GMEM_CONVERSIONS.
Quoted 4 lines
Setting
GUEST_MEMFD_FLAG_INIT_PRIVATEDocumentation/virt/kvm/api.rst +3
-0+will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
[...]
Quoted 10 lines
static int
kvm_gmem_init_fs_context(struct fs_context *fc)Documentation/virt/kvm/api.rst +2
-0@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = { static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, loff_t size, u64 flags) { + struct kvm_gmem_inode_private *private; struct inode *inode; + int err;
Documentation/virt/kvm/api.rst static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 10 lines
Documentation/virt/kvm/api.rst +6
-0inode = alloc_anon_secure_inode(kvm_gmem_mnt->mnt_sb, name); if (IS_ERR(inode)) return inode; + err = -ENOMEM; + private = kzalloc(sizeof(*private), GFP_KERNEL); + if (!private) + goto out; + + mt_init(&private->shareability);
shareability is defined only when CONFIG_KVM_GMEM_SHARED_MEM enabled, should be done within CONFIG_KVM_GMEM_SHARED_MEM .
Quoted 21 lines
Documentation/virt/kvm/api.rst +11
-0+ inode->i_mapping->i_private_data = private; + + err = kvm_gmem_shareability_setup(private, size, flags); + if (err) + goto out; + inode->i_private = (void *)(unsigned long)flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping)); return inode; + +out: + iput(inode); + + return ERR_PTR(err); }
[...]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 35 lines · Binbin Wu
Quoted 32 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f---
Documentation/virt/kvm/api.rst| 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++-3 files changed, 129 insertions(+), 2 deletions(-)
Quoted 12 lines
Quoted 9 lines
Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr.The use of
GUEST_MEMFD_FLAG_SUPPORT_SHAREDwill not be allowed for CoCo VMs. This is validated when theguest_memfdinstance is bound to the VM. +If the capabilityKVM_CAP_GMEM_CONVERSIONSis supported, then the 'flags' field +supportsGUEST_MEMFD_FLAG_INIT_PRIVATE.It seems that the sentence is stale?
Didn't find the definition ofKVM_CAP_GMEM_CONVERSIONS.
Aha! It's a typo, should be KVM_CAP_GMEM_CONVERSION.
Quoted 48 lines
Quoted 5 lines
Setting
GUEST_MEMFD_FLAG_INIT_PRIVATEDocumentation/virt/kvm/api.rst +3
-0+will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +[...]
Quoted 7 lines
static int
kvm_gmem_init_fs_context(struct fs_context *fc)Documentation/virt/kvm/api.rst +1
-0@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = { static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, loff_t size, u64 flags) { + struct kvm_gmem_inode_private *private; struct inode *inode;Documentation/virt/kvm/api.rst static const struct inode_operations kvm_gmem_iops = { · line 659
Quoted 10 lines
Documentation/virt/kvm/api.rst +6
-0+ int err; inode = alloc_anon_secure_inode(kvm_gmem_mnt->mnt_sb, name); if (IS_ERR(inode)) return inode; + err = -ENOMEM; + private = kzalloc(sizeof(*private), GFP_KERNEL); + if (!private) + goto out; + + mt_init(&private->shareability);shareability is defined only when
CONFIG_KVM_GMEM_SHARED_MEMenabled, should be done withinCONFIG_KVM_GMEM_SHARED_MEM.Quoted 19 lines
Documentation/virt/kvm/api.rst +11
-0+ inode->i_mapping->i_private_data = private; + + err = kvm_gmem_shareability_setup(private, size, flags); + if (err) + goto out; + inode->i_private = (void *)(unsigned long)flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping)); return inode; + +out: + iput(inode); + + return ERR_PTR(err); }[...]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Binbin WuQuoted 41 lines · Binbin Wu
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.
Quoted 6 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6412
Quoted 2 lines
Documentation/virt/kvm/api.rst +2
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE.It seems that the sentence is stale?
Didn't find the definition ofKVM_CAP_GMEM_CONVERSIONS.
Thanks. This should read
If the capability KVM_CAP_GMEM_SHARED_MEM is supported, and GUEST_MEMFD_FLAG_SUPPORT_SHARED is specified, then the 'flags' field supports GUEST_MEMFD_FLAG_INIT_PRIVATE.
Quoted 19 lines
Quoted 4 lines
Setting
GUEST_MEMFD_FLAG_INIT_PRIVATEDocumentation/virt/kvm/api.rst +3
-0+will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +[...]
Quoted 14 lines
static int
kvm_gmem_init_fs_context(struct fs_context *fc)Documentation/virt/kvm/api.rst +2
-0@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = { static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, loff_t size, u64 flags) { + struct kvm_gmem_inode_private *private; struct inode *inode; + int err; inode = alloc_anon_secure_inode(kvm_gmem_mnt->mnt_sb, name); if (IS_ERR(inode)) return inode;
Quoted 10 lines
Documentation/virt/kvm/api.rst static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 6 lines
Documentation/virt/kvm/api.rst +6
-0+ err = -ENOMEM; + private = kzalloc(sizeof(*private), GFP_KERNEL); + if (!private) + goto out; + + mt_init(&private->shareability);shareability is defined only when
CONFIG_KVM_GMEM_SHARED_MEMenabled, should be done withinCONFIG_KVM_GMEM_SHARED_MEM.
Yes, thank you! Will also update this to only initialize shareability if (flags & GUEST_MEMFD_FLAG_SUPPORT_SHARED).
Quoted 22 lines
Quoted 21 lines
Documentation/virt/kvm/api.rst +11
-0+ inode->i_mapping->i_private_data = private; + + err = kvm_gmem_shareability_setup(private, size, flags); + if (err) + goto out; + inode->i_private = (void *)(unsigned long)flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping)); return inode; + +out: + iput(inode); + + return ERR_PTR(err); }[...]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 50 lines · Ackerley Tng
Quoted 41 lines · Binbin Wu
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 6 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6412
Quoted 2 lines
Documentation/virt/kvm/api.rst +2
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE.It seems that the sentence is stale?
Didn't find the definition ofKVM_CAP_GMEM_CONVERSIONS.
Quoted 6 lines
Thanks. This should read
If the capability
KVM_CAP_GMEM_SHARED_MEMis supported, andGUEST_MEMFD_FLAG_SUPPORT_SHAREDis specified, then the 'flags' field supportsGUEST_MEMFD_FLAG_INIT_PRIVATE.
My bad, saw your other email. Fixing the above:
If the capability KVM_CAP_GMEM_CONVERSION is supported, and GUEST_MEMFD_FLAG_SUPPORT_SHARED is specified, then the 'flags' field supports GUEST_MEMFD_FLAG_INIT_PRIVATE.
Quoted 55 lines
Quoted 19 lines
Quoted 4 lines
Setting
GUEST_MEMFD_FLAG_INIT_PRIVATEDocumentation/virt/kvm/api.rst +3
-0+will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +[...]
Quoted 14 lines
static int
kvm_gmem_init_fs_context(struct fs_context *fc)Documentation/virt/kvm/api.rst +2
-0@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = { static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, loff_t size, u64 flags) { + struct kvm_gmem_inode_private *private; struct inode *inode; + int err; inode = alloc_anon_secure_inode(kvm_gmem_mnt->mnt_sb, name); if (IS_ERR(inode)) return inode;Quoted 10 lines
Documentation/virt/kvm/api.rst static const struct inode_operations kvm_gmem_iops = { · line 661
Quoted 6 lines
Documentation/virt/kvm/api.rst +6
-0+ err = -ENOMEM; + private = kzalloc(sizeof(*private), GFP_KERNEL); + if (!private) + goto out; + + mt_init(&private->shareability);shareability is defined only when
CONFIG_KVM_GMEM_SHARED_MEMenabled, should be done withinCONFIG_KVM_GMEM_SHARED_MEM.Yes, thank you! Will also update this to only initialize shareability if (flags &
GUEST_MEMFD_FLAG_SUPPORT_SHARED).Quoted 22 lines
Quoted 21 lines
Documentation/virt/kvm/api.rst +11
-0+ inode->i_mapping->i_private_data = private; + + err = kvm_gmem_shareability_setup(private, size, flags); + if (err) + goto out; + inode->i_private = (void *)(unsigned long)flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, WARN_ON_ONCE(!mapping_unevictable(inode->i_mapping)); return inode; + +out: + iput(inode); + + return ERR_PTR(err); }[...]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngQuoted 37 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 8 lines
Documentation/virt/kvm/api.rst +5
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM. +If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSION doesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Quoted 61 lines
Documentation/virt/kvm/api.rst +0
-0See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +22
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private,
virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+ loff_t size, u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );
One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via KVM_SET_MEMORY_ATTRIBUTES (which is the current QEMU default behavior).
I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
Maybe it would make sense for KVM memory attributes too?
Quoted 135 lines
virt/kvm/guest_memfd.c +87
-1+} + +static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." ); + +return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability); + inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;
virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 5 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +
I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new SHAREABILITY_GUEST_PREPARED state) the SHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPARED transition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.
I was originally planning on introducing a new rw_semaphore with similar semantics to the rw_lock that Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle setting SHAREABILITY_GUEST_PREPARED within fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.
They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM: guest_memfd: Add CAP KVM_CAP_GMEM_CONVERSION
KVM: Query guest_memfd for private/shared status
KVM: guest_memfd: Skip LRU for guest_memfd folios
KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM: guest_memfd: Introduce and use shareability to guard faulting
KVM: guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodes
but figured I'd mention it here in case there are other things to consider on the locking front.
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/
Quoted 16 lines
virt/kvm/guest_memfd.c +1
-1folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; }Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.
Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.
I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Because shared pages are split once any memory is allocated, having a way to INIT_PRIVATE could avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?
I guess we could also have userspace be careful not to do any allocation before converting.
Quoted 293 lines
Quoted 61 lines
Documentation/virt/kvm/api.rst +0
-0See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +22
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private,virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+ loff_t size, u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via
KVM_SET_MEMORY_ATTRIBUTES(which is the current QEMU default behavior).I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
Maybe it would make sense for KVM memory attributes too?
Quoted 135 lines
virt/kvm/guest_memfd.c +87
-1+} + +static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." ); + +return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability); + inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 5 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new
SHAREABILITY_GUEST_PREPAREDstate) theSHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPAREDtransition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.I was originally planning on introducing a new
rw_semaphorewith similar semantics to therw_lockthat Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle settingSHAREABILITY_GUEST_PREPAREDwithin fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodesbut figured I'd mention it here in case there are other things to consider on the locking front.
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/Quoted 16 lines
virt/kvm/guest_memfd.c +1
-1folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; }Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 68 lines · Ackerley Tng
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Quoted 7 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?I guess we could also have userspace be careful not to do any allocation before converting.
I assume we do want to support things like preallocating guest memory so not sure this approach is feasible to avoid splits.
But I feel like we might be working around a deeper issue here, which is that we are pre-emptively splitting anything that *could* be mapped into userspace (i.e. allocated+shared/mixed), rather than splitting when necessary.
I know that was the plan laid out in the guest_memfd calls, but I've run into a couple instances that have me thinking we should revisit this.
1) Some of the recent guest_memfd seems to be gravitating towards having userspace populate/initialize guest memory payload prior to boot via mmap()'ing the shared guest_memfd pages so things work the same as they would for initialized normal VM memory payload (rather than relying on back-channels in the kernel to user data into guest_memfd pages).
When you do this though, for an SNP guest at least, that memory acceptance is done in chunks of 4MB (with accept_memory=lazy), and because that will put each 1GB page into an allocated+mixed state, we end up splitting every 1GB to 4K and the guest can't even accept/PVALIDATE it 2MB at that point even if userspace doesn't touch anything in the range. As some point the guest will convert/accept the entire range, at which point we could merge, but for SNP we'd need guest cooperation to actually use a higher-granularity in stage2 page tables at that point since RMP entries are effectively all split to 4K.
I understand the intent is to default to private where this wouldn't be an issue, and we could punt to userspace to deal with it, but it feels like an artificial restriction to place on userspace. And if we do want to allow/expect guest_memfd contents to be initialized pre-boot just like normal memory, then userspace would need to jump through some hoops:
- if defaulting to private: add hooks to convert each range that's being modified to a shared state prior to writing to it
- if defaulting to shared: initialize memory in-place, then covert everything else to private to avoid unecessarily splitting folios at run-time
It feels like implementations details are bleeding out into the API to some degree here (e.g. we'd probably at least need to document this so users know how to take proper advantage of hugepage support).
2) There are some use-cases for HugeTLB + CoCo that have come to my attention recently that put a lot of weight on still being able to maximize mapping/hugepage size when accessing shared mem from userspace, e.g. for certain DPDK workloads that accessed shared guest buffers from host userspace. We don't really have a story for this, and I wouldn't expect us to at this stage, but I think it ties into #1 so might be worth considering in that context.
I'm still fine with the current approach as a starting point, but I'm wondering if improving both #1/#2 might not be so bad and maybe even give us some more flexibility (for instance, Sean had mentioned leaving open the option of tracking more than just shareability/mappability, and if there is split/merge logic associated with those transitions then re-scanning each of these attributes for a 1G range seems like it could benefit from some sort of intermediate data structure to help determine things like what mapping granularity is available for guest/userspace for a particular range.
One approach I was thinking of was that we introduce a data structure similar to KVM's memslot->arch.lpage_info() where we store information about what 1G/2M ranges are shared/private/mixed, and then instead of splitting ahead of time we just record that state into this data structure (using the same write lock as with the shareability/mappability state), and then at *fault* time we split the folio if our lpage_info-like data structure says the range is mixed.
Then, if guest converts a 2M/4M range to private while lazilly-accepting (for instance), we can still keep the folio intact as 1GB, but mark the 1G range in the lpage_info-like data structure as mixed so that we still inform KVM/etc. they need to map it as 2MB or lower in stage2 page tables. In that case, even at guest fault-time, we can leave the folio unsplit until userspace tries to touch it (though in most cases it never will and we can keep most of the guest's 1G intact for the duration of its lifetime).
On the userspace side, another nice thing there is if we see 1G is in a mixed state, but 2M is all-shared, then we can still leave the folio as 2M, and I think the refcount'ing logic would still work for the most part, which makes #2 a bit easier to implement as well.
And of course, we wouldn't need the INIT_PRIVATE then since we are only splitting when necessary.
But I guess this all comes down to how much extra pain there is in tracking a 1G folio that's been split into a mixed of 2MB/4K regions, but I think we'd get a lot more mileage out of getting that working and just completely stripping out all of the merging logic for initial implementation (other than at cleanup time), so maybe complexity-wise it balances out a bit?
Thanks,
Mike
Quoted 295 lines
Quoted 294 lines
Quoted 62 lines
Documentation/virt/kvm/api.rst +0
-0See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +22
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private,virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+ loff_t size, u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via
KVM_SET_MEMORY_ATTRIBUTES(which is the current QEMU default behavior).I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
Maybe it would make sense for KVM memory attributes too?
Quoted 135 lines
virt/kvm/guest_memfd.c +87
-1+} + +static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." ); + +return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability); + inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 5 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new
SHAREABILITY_GUEST_PREPAREDstate) theSHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPAREDtransition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.I was originally planning on introducing a new
rw_semaphorewith similar semantics to therw_lockthat Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle settingSHAREABILITY_GUEST_PREPAREDwithin fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodesbut figured I'd mention it here in case there are other things to consider on the locking front.
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/Quoted 16 lines
virt/kvm/guest_memfd.c +1
-1folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; }Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 96 lines · Michael Roth
Quoted 68 lines · Ackerley Tng
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Quoted 7 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?I guess we could also have userspace be careful not to do any allocation before converting.
I assume we do want to support things like preallocating guest memory so not sure this approach is feasible to avoid splits.
But I feel like we might be working around a deeper issue here, which is that we are pre-emptively splitting anything that *could* be mapped into userspace (i.e. allocated+shared/mixed), rather than splitting when necessary.
I know that was the plan laid out in the
guest_memfdcalls, but I've run into a couple instances that have me thinking we should revisit this.1) Some of the recent
guest_memfdseems to be gravitating towards having userspace populate/initialize guest memory payload prior to boot via mmap()'ing the sharedguest_memfdpages so things work the same as they would for initialized normal VM memory payload (rather than relying on back-channels in the kernel to user data intoguest_memfdpages).
Quoted 3 lines
When you do this though, for an SNP guest at least, that memory acceptance is done in chunks of 4MB (with accept_memory=lazy), and because that will put each 1GB page into an allocated+mixed state,
I would like your help in understanding why we need to start guest_memfd ranges as shared for SNP guests. guest_memfd ranges being private simply should mean that certain ranges are not faultable by the userspace.
Will following work?
1) Userspace starts all guest_memfd ranges as private.
2) During early guest boot it starts issuing PSC requests for converting memory from shared to private
-> KVM forwards this request to userspace
-> Userspace checks that the pages are already private and simplydoes nothing.
3) Pvalidate from guest on that memory will result in guest_memfd offset query which will cause the RMP table entries to actually get populated.
Quoted 15 lines
we end up splitting every 1GB to 4K and the guest can't even accept/PVALIDATE it 2MB at that point even if userspace doesn't touch anything in the range. As some point the guest will convert/accept the entire range, at which point we could merge, but for SNP we'd need guest cooperation to actually use a higher-granularity in stage2 page tables at that point since RMP entries are effectively all split to 4K.
I understand the intent is to default to private where this wouldn't be an issue, and we could punt to userspace to deal with it, but it feels like an artificial restriction to place on userspace. And if we do want to allow/expect
guest_memfdcontents to be initialized pre-boot just like normal memory, then userspace would need to jump through some hoops:
Quoted 2 lines
- if defaulting to private: add hooks to convert each range that's being modified to a shared state prior to writing to it
Why is that a problem?
Quoted 7 lines
- if defaulting to shared: initialize memory in-place, then covert everything else to private to avoid unecessarily splitting folios at run-time
It feels like implementations details are bleeding out into the API to some degree here (e.g. we'd probably at least need to document this so users know how to take proper advantage of hugepage support).
Does it make sense to keep the default behavior as INIT_PRIVATE for SNP VMs always even without using hugepages?
Quoted 8 lines
2) There are some use-cases for HugeTLB + CoCo that have come to my attention recently that put a lot of weight on still being able to maximize mapping/hugepage size when accessing shared mem from userspace, e.g. for certain DPDK workloads that accessed shared guest buffers from host userspace. We don't really have a story for this, and I wouldn't expect us to at this stage, but I think it ties into #1 so might be worth considering in that context.
Major problem I see here is that if anything in the kernel does a GUP on shared memory ranges (which is very likely to happen), it would be difficult to get them to let go of the whole hugepage before it can be split safely.
Another problem is guest_memfd today doesn't support management of large user space page table mappings, this can turnout to be significant work to do referring to hugetlb pagetable management logic.
Quoted 342 lines
I'm still fine with the current approach as a starting point, but I'm wondering if improving both #1/#2 might not be so bad and maybe even give us some more flexibility (for instance, Sean had mentioned leaving open the option of tracking more than just shareability/mappability, and if there is split/merge logic associated with those transitions then re-scanning each of these attributes for a 1G range seems like it could benefit from some sort of intermediate data structure to help determine things like what mapping granularity is available for guest/userspace for a particular range.
One approach I was thinking of was that we introduce a data structure similar to KVM's
memslot->arch.lpage_info()where we store information about what 1G/2M ranges are shared/private/mixed, and then instead of splitting ahead of time we just record that state into this data structure (using the same write lock as with the shareability/mappability state), and then at *fault* time we split the folio if our lpage_info-like data structure says the range is mixed.Then, if guest converts a 2M/4M range to private while lazilly-accepting (for instance), we can still keep the folio intact as 1GB, but mark the 1G range in the lpage_info-like data structure as mixed so that we still inform KVM/etc. they need to map it as 2MB or lower in stage2 page tables. In that case, even at guest fault-time, we can leave the folio unsplit until userspace tries to touch it (though in most cases it never will and we can keep most of the guest's 1G intact for the duration of its lifetime).
On the userspace side, another nice thing there is if we see 1G is in a mixed state, but 2M is all-shared, then we can still leave the folio as 2M, and I think the refcount'ing logic would still work for the most part, which makes #2 a bit easier to implement as well.
And of course, we wouldn't need the
INIT_PRIVATEthen since we are only splitting when necessary.But I guess this all comes down to how much extra pain there is in tracking a 1G folio that's been split into a mixed of 2MB/4K regions, but I think we'd get a lot more mileage out of getting that working and just completely stripping out all of the merging logic for initial implementation (other than at cleanup time), so maybe complexity-wise it balances out a bit?
Thanks,
Mike
Quoted 295 lines
Quoted 294 lines
Quoted 62 lines
Documentation/virt/kvm/api.rst +0
-0See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +22
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private,virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+ loff_t size, u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via
KVM_SET_MEMORY_ATTRIBUTES(which is the current QEMU default behavior).I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
Maybe it would make sense for KVM memory attributes too?
Quoted 135 lines
virt/kvm/guest_memfd.c +87
-1+} + +static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." ); + +return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability); + inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 5 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new
SHAREABILITY_GUEST_PREPAREDstate) theSHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPAREDtransition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.I was originally planning on introducing a new
rw_semaphorewith similar semantics to therw_lockthat Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle settingSHAREABILITY_GUEST_PREPAREDwithin fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodesbut figured I'd mention it here in case there are other things to consider on the locking front.
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/Quoted 16 lines
virt/kvm/guest_memfd.c +1
-2folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; } --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 10 lines · Vishal Annapurve
...
Quoted 8 lines
2) There are some use-cases for HugeTLB + CoCo that have come to my attention recently that put a lot of weight on still being able to maximize mapping/hugepage size when accessing shared mem from userspace, e.g. for certain DPDK workloads that accessed shared guest buffers from host userspace. We don't really have a story for this, and I wouldn't expect us to at this stage, but I think it ties into #1 so might be worth considering in that context.
Quoted 4 lines
Major problem I see here is that if anything in the kernel does a GUP on shared memory ranges (which is very likely to happen), it would be difficult to get them to let go of the whole hugepage before it can be split safely.
The scenario I was alluding to here:
guest trying to convert a subpage from a shared range backed by hugepage to private.
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Vishal AnnapurveQuoted 71 lines · Vishal Annapurve
Quoted 70 lines · Michael Roth
Quoted 68 lines · Ackerley Tng
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use mmap() to populate memory (in which case you can pretty much ignore my previous response))
I'm still not sure where the INIT_PRIVATE flag comes into play. For SNP, userspace already defaults to marking everything private pretty close to guest_memfd creation time, so the potential for allocations to occur in-between seems small, but worth confirming.
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating guest_memfd memory (and even pre-faulting via KVM_PRE_FAULT_MEMORY), but I think that could still work right? The fallocate() handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for that fallocate() to happen before userspace does the "manually" shared->private conversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?
Quoted 19 lines
Quoted 19 lines
I assume we do want to support things like preallocating guest memory so not sure this approach is feasible to avoid splits.
But I feel like we might be working around a deeper issue here, which is that we are pre-emptively splitting anything that *could* be mapped into userspace (i.e. allocated+shared/mixed), rather than splitting when necessary.
I know that was the plan laid out in the
guest_memfdcalls, but I've run into a couple instances that have me thinking we should revisit this.1) Some of the recent
guest_memfdseems to be gravitating towards having userspace populate/initialize guest memory payload prior to boot via mmap()'ing the sharedguest_memfdpages so things work the same as they would for initialized normal VM memory payload (rather than relying on back-channels in the kernel to user data intoguest_memfdpages).
Quoted 8 lines
Quoted 3 lines
When you do this though, for an SNP guest at least, that memory acceptance is done in chunks of 4MB (with accept_memory=lazy), and because that will put each 1GB page into an allocated+mixed state,
I would like your help in understanding why we need to start
guest_memfdranges as shared for SNP guests.guest_memfdranges being private simply should mean that certain ranges are not faultable by the userspace.
It's seeming like I probably misremembered, but I thought there was a discussion on guest_memfd call a month (or so?) ago about whether to continue to use backchannels to populate guest_memfd pages prior to launch. It was in the context of whether to keep using kvm_gmem_populate() for populating guest_memfd pages by copying them in from separate userspace buffer vs. simply populating them directly from userspace. I thought we were leaning on the latter since it was simpler all-around, which is great for SNP since that is already how it populates memory: by writing to it from userspace, which kvm_gmem_populate() then copies into guest_memfd pages. With shared gmem support, we just skip the latter now in the kernel rather needing changes to how userspace handles things in that regard. But maybe that was just wishful thinking :)
But you raise some very compelling points on why this might not be a good idea even if that was how that discussion went.
Quoted 11 lines
Will following work?
1) Userspace starts allguest_memfdranges as private.
2) During early guest boot it starts issuing PSC requests for converting memory from shared to private-> KVM forwards this request to userspace -> Userspace checks that the pages are already private and simplydoes nothing.
3) Pvalidate from guest on that memory will result inguest_memfdoffset query which will cause the RMP table entries to actually get populated.
That would work, but there will need to be changes on userspace to deal with how SNP populates memory pre-boot just like normal VMs do. We will instead need to copy that data into separate buffers, and pass those in as the buffer hva instead of the shared hva corresponding to that GPA.
But that seems reasonable if it avoids so many other problems.
Quoted 16 lines
Quoted 16 lines
we end up splitting every 1GB to 4K and the guest can't even accept/PVALIDATE it 2MB at that point even if userspace doesn't touch anything in the range. As some point the guest will convert/accept the entire range, at which point we could merge, but for SNP we'd need guest cooperation to actually use a higher-granularity in stage2 page tables at that point since RMP entries are effectively all split to 4K.
I understand the intent is to default to private where this wouldn't be an issue, and we could punt to userspace to deal with it, but it feels like an artificial restriction to place on userspace. And if we do want to allow/expect
guest_memfdcontents to be initialized pre-boot just like normal memory, then userspace would need to jump through some hoops:
Quoted 4 lines
Quoted 2 lines
- if defaulting to private: add hooks to convert each range that's being modified to a shared state prior to writing to it
Why is that a problem?
These were only problems if we went the above-mentioned way of populating memory pre-boot via mmap() instead of other backchannels. If we don't do that, then both these things cease to be problems. Sounds goods to me. :)
Quoted 11 lines
Quoted 8 lines
- if defaulting to shared: initialize memory in-place, then covert everything else to private to avoid unecessarily splitting folios at run-time
It feels like implementations details are bleeding out into the API to some degree here (e.g. we'd probably at least need to document this so users know how to take proper advantage of hugepage support).
Does it make sense to keep the default behavior as
INIT_PRIVATEfor SNP VMs always even without using hugepages?
Yes!
Though, revisiting discussion around INIT_PRIVATE (without the baggage of potentially relying on mmap() to populate memory), I'm still not sure why it's needed. I responded in the context of Ackerley's initial reply above.
Quoted 15 lines
Quoted 9 lines
2) There are some use-cases for HugeTLB + CoCo that have come to my attention recently that put a lot of weight on still being able to maximize mapping/hugepage size when accessing shared mem from userspace, e.g. for certain DPDK workloads that accessed shared guest buffers from host userspace. We don't really have a story for this, and I wouldn't expect us to at this stage, but I think it ties into #1 so might be worth considering in that context.
Major problem I see here is that if anything in the kernel does a GUP on shared memory ranges (which is very likely to happen), it would be difficult to get them to let go of the whole hugepage before it can be split safely.
Quoted 4 lines
Another problem is
guest_memfdtoday doesn't support management of large user space page table mappings, this can turnout to be significant work to do referring to hugetlb pagetable management logic.
Yah that was more line-of-sight that might be possible by going this route, but the refcount'ing issue above is a showstopper as always. I'd somehow convinced myself that supporting fine-grained splitting somehow worked around it, but you still have no idea what page you need to avoid converting and fancy splitting doesn't get you past that. More wishful thinking. =\
Thanks,
Mike
Quoted 343 lines
Quoted 343 lines
I'm still fine with the current approach as a starting point, but I'm wondering if improving both #1/#2 might not be so bad and maybe even give us some more flexibility (for instance, Sean had mentioned leaving open the option of tracking more than just shareability/mappability, and if there is split/merge logic associated with those transitions then re-scanning each of these attributes for a 1G range seems like it could benefit from some sort of intermediate data structure to help determine things like what mapping granularity is available for guest/userspace for a particular range.
One approach I was thinking of was that we introduce a data structure similar to KVM's
memslot->arch.lpage_info()where we store information about what 1G/2M ranges are shared/private/mixed, and then instead of splitting ahead of time we just record that state into this data structure (using the same write lock as with the shareability/mappability state), and then at *fault* time we split the folio if our lpage_info-like data structure says the range is mixed.Then, if guest converts a 2M/4M range to private while lazilly-accepting (for instance), we can still keep the folio intact as 1GB, but mark the 1G range in the lpage_info-like data structure as mixed so that we still inform KVM/etc. they need to map it as 2MB or lower in stage2 page tables. In that case, even at guest fault-time, we can leave the folio unsplit until userspace tries to touch it (though in most cases it never will and we can keep most of the guest's 1G intact for the duration of its lifetime).
On the userspace side, another nice thing there is if we see 1G is in a mixed state, but 2M is all-shared, then we can still leave the folio as 2M, and I think the refcount'ing logic would still work for the most part, which makes #2 a bit easier to implement as well.
And of course, we wouldn't need the
INIT_PRIVATEthen since we are only splitting when necessary.But I guess this all comes down to how much extra pain there is in tracking a 1G folio that's been split into a mixed of 2MB/4K regions, but I think we'd get a lot more mileage out of getting that working and just completely stripping out all of the merging logic for initial implementation (other than at cleanup time), so maybe complexity-wise it balances out a bit?
Thanks,
Mike
Quoted 295 lines
Quoted 294 lines
Quoted 62 lines
Documentation/virt/kvm/api.rst +0
-0See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +22
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private,virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+ loff_t size, u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via
KVM_SET_MEMORY_ATTRIBUTES(which is the current QEMU default behavior).I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
Maybe it would make sense for KVM memory attributes too?
Quoted 135 lines
virt/kvm/guest_memfd.c +87
-1+} + +static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." ); + +return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability); + inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 5 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new
SHAREABILITY_GUEST_PREPAREDstate) theSHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPAREDtransition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.I was originally planning on introducing a new
rw_semaphorewith similar semantics to therw_lockthat Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle settingSHAREABILITY_GUEST_PREPAREDwithin fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodesbut figured I'd mention it here in case there are other things to consider on the locking front.
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/Quoted 16 lines
virt/kvm/guest_memfd.c +1
-2folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; } --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 78 lines · Michael Roth
Quoted 76 lines · Vishal Annapurve
Quoted 75 lines · Michael Roth
Quoted 73 lines · Ackerley Tng
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?
Quoted 6 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use
mmap()to populate memory (in which case you can pretty much ignore my previous response))
I am assuming in-place conversion with huge page backing for the discussion below.
Looks like there are three scenarios/usecases we are discussing here:
1) Pre-allocating guest_memfd file offsets
- Userspace can use fallocate to do this for hugepages by keeping
the file ranges marked private.
2) Prefaulting guest EPT/NPT entries
3) Populating initial guest payload into guest_memfd memory
- Userspace can mark certain ranges as shared, populate the
contents and convert the ranges back to private. So mmap will come in handy here.
Quoted 5 lines
I'm still not sure where the
INIT_PRIVATEflag comes into play. For SNP, userspace already defaults to marking everything private pretty close toguest_memfdcreation time, so the potential for allocations to occur in-between seems small, but worth confirming.
Ok, I am not much worried about whether the INIT_PRIVATE flag gets supported or not, but more about the default setting that different CVMs start with. To me, it looks like all CVMs should start as everything private by default and if there is a way to bake that configuration during guest_memfd creation time that would be good to have instead of doing "create and convert" operations and there is a fairly low cost to support this flag.
Quoted 9 lines
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating
guest_memfdmemory (and even pre-faulting viaKVM_PRE_FAULT_MEMORY), but I think that could still work right? Thefallocate()handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for thatfallocate()to happen before userspace does the "manually"shared->privateconversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?
This usecase of being able to preallocate should still work with in-place conversion assuming all ranges are private before pre-population.
Quoted 29 lines
Quoted 20 lines
Quoted 20 lines
I assume we do want to support things like preallocating guest memory so not sure this approach is feasible to avoid splits.
But I feel like we might be working around a deeper issue here, which is that we are pre-emptively splitting anything that *could* be mapped into userspace (i.e. allocated+shared/mixed), rather than splitting when necessary.
I know that was the plan laid out in the
guest_memfdcalls, but I've run into a couple instances that have me thinking we should revisit this.1) Some of the recent
guest_memfdseems to be gravitating towards having userspace populate/initialize guest memory payload prior to boot via mmap()'ing the sharedguest_memfdpages so things work the same as they would for initialized normal VM memory payload (rather than relying on back-channels in the kernel to user data intoguest_memfdpages).Quoted 8 lines
Quoted 3 lines
When you do this though, for an SNP guest at least, that memory acceptance is done in chunks of 4MB (with accept_memory=lazy), and because that will put each 1GB page into an allocated+mixed state,
I would like your help in understanding why we need to start
guest_memfdranges as shared for SNP guests.guest_memfdranges being private simply should mean that certain ranges are not faultable by the userspace.
Quoted 12 lines
It's seeming like I probably misremembered, but I thought there was a discussion on
guest_memfdcall a month (or so?) ago about whether to continue to use backchannels to populateguest_memfdpages prior to launch. It was in the context of whether to keep usingkvm_gmem_populate()for populatingguest_memfdpages by copying them in from separate userspace buffer vs. simply populating them directly from userspace. I thought we were leaning on the latter since it was simpler all-around, which is great for SNP since that is already how it populates memory: by writing to it from userspace, whichkvm_gmem_populate()then copies intoguest_memfdpages. With shared gmem support, we just skip the latter now in the kernel rather needing changes to how userspace handles things in that regard. But maybe that was just wishful thinking :)
You remember it correctly and that's how userspace should pre-populate guest memory contents with in-place conversion support available. Userspace can simply do the following scheme as an example:
1) Create guest_memfd with the INIT_PRIVATE flag or if we decide to not go that way, create a guest_memfd file and set all ranges as private.
2) Preallocate the guest_memfd ranges.
3) Convert the needed ranges to shared, populate the initial guest payload and then convert those ranges back to private.
Important point here is that guest_memfd ranges can be marked as private before pre-allocating guest_memfd ranges.
Quoted 16 lines
But you raise some very compelling points on why this might not be a good idea even if that was how that discussion went.
Quoted 11 lines
Will following work?
1) Userspace starts allguest_memfdranges as private.
2) During early guest boot it starts issuing PSC requests for converting memory from shared to private-> KVM forwards this request to userspace -> Userspace checks that the pages are already private and simplydoes nothing.
3) Pvalidate from guest on that memory will result inguest_memfdoffset query which will cause the RMP table entries to actually get populated.
Quoted 4 lines
That would work, but there will need to be changes on userspace to deal with how SNP populates memory pre-boot just like normal VMs do. We will instead need to copy that data into separate buffers, and pass those in as the buffer hva instead of the shared hva corresponding to that GPA.
Initial guest memory payload generally carries a much smaller footprint so I ignored that detail in the above sequence. As I said above, userspace should be able to use guest_memfd ranges to directly populate contents by converting those ranges to shared.
Quoted 22 lines
But that seems reasonable if it avoids so many other problems.
Quoted 19 lines
Quoted 18 lines
we end up splitting every 1GB to 4K and the guest can't even accept/PVALIDATE it 2MB at that point even if userspace doesn't touch anything in the range. As some point the guest will convert/accept the entire range, at which point we could merge, but for SNP we'd need guest cooperation to actually use a higher-granularity in stage2 page tables at that point since RMP entries are effectively all split to 4K.
I understand the intent is to default to private where this wouldn't be an issue, and we could punt to userspace to deal with it, but it feels like an artificial restriction to place on userspace. And if we do want to allow/expect
guest_memfdcontents to be initialized pre-boot just like normal memory, then userspace would need to jump through some hoops:- if defaulting to private: add hooks to convert each range that's being modified to a shared state prior to writing to it
Quoted 6 lines
Quoted 1 line
Why is that a problem?
These were only problems if we went the above-mentioned way of populating memory pre-boot via
mmap()instead of other backchannels. If we don't do that, then both these things cease to be problems. Sounds goods to me. :)
I think there wouldn't be a problem even if we pre-populated memory pre-boot via mmap(). Using mmap() seems a preferable option to me.
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 123 lines · Vishal Annapurve
Quoted 78 lines · Michael Roth
Quoted 76 lines · Vishal Annapurve
Quoted 75 lines · Michael Roth
Quoted 73 lines · Ackerley Tng
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?Quoted 6 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use
mmap()to populate memory (in which case you can pretty much ignore my previous response))I am assuming in-place conversion with huge page backing for the discussion below.
Looks like there are three scenarios/usecases we are discussing here:
1) Pre-allocatingguest_memfdfile offsets
- Userspace can use fallocate to do this for hugepages by keeping
the file ranges marked private.
2) Prefaulting guest EPT/NPT entries
3) Populating initial guest payload intoguest_memfdmemory
- Userspace can mark certain ranges as shared, populate the
contents and convert the ranges back to private. So mmap will come in handy here.Quoted 5 lines
I'm still not sure where the
INIT_PRIVATEflag comes into play. For SNP, userspace already defaults to marking everything private pretty close toguest_memfdcreation time, so the potential for allocations to occur in-between seems small, but worth confirming.Ok, I am not much worried about whether the
INIT_PRIVATEflag gets supported or not, but more about the default setting that different CVMs start with. To me, it looks like all CVMs should start as everything private by default and if there is a way to bake that configuration duringguest_memfdcreation time that would be good to have instead of doing "create and convert" operations and there is a fairly low cost to support this flag.Quoted 9 lines
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating
guest_memfdmemory (and even pre-faulting viaKVM_PRE_FAULT_MEMORY), but I think that could still work right? Thefallocate()handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for thatfallocate()to happen before userspace does the "manually"shared->privateconversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?
Quoted 3 lines
This usecase of being able to preallocate should still work with in-place conversion assuming all ranges are private before pre-population.
Ok, I think I was missing that the merge logic here will then restore it to 1GB before the guest starts, so the folio isn't permanently split if we do the mmap() and that gives us more flexibility on how we can use it.
I was thinking we needed to avoid the split from the start by avoiding paths like mmap() which might trigger the split. I was trying to avoid any merge->unsplit logic in the THP case (or unsplit in general), in which case we'd get permanent splits via the mmap() approach, but for 2MB that's probably not a big deal.
Quoted 53 lines
Quoted 30 lines
Quoted 21 lines
Quoted 21 lines
I assume we do want to support things like preallocating guest memory so not sure this approach is feasible to avoid splits.
But I feel like we might be working around a deeper issue here, which is that we are pre-emptively splitting anything that *could* be mapped into userspace (i.e. allocated+shared/mixed), rather than splitting when necessary.
I know that was the plan laid out in the
guest_memfdcalls, but I've run into a couple instances that have me thinking we should revisit this.1) Some of the recent
guest_memfdseems to be gravitating towards having userspace populate/initialize guest memory payload prior to boot via mmap()'ing the sharedguest_memfdpages so things work the same as they would for initialized normal VM memory payload (rather than relying on back-channels in the kernel to user data intoguest_memfdpages).Quoted 8 lines
Quoted 3 lines
When you do this though, for an SNP guest at least, that memory acceptance is done in chunks of 4MB (with accept_memory=lazy), and because that will put each 1GB page into an allocated+mixed state,
I would like your help in understanding why we need to start
guest_memfdranges as shared for SNP guests.guest_memfdranges being private simply should mean that certain ranges are not faultable by the userspace.Quoted 12 lines
It's seeming like I probably misremembered, but I thought there was a discussion on
guest_memfdcall a month (or so?) ago about whether to continue to use backchannels to populateguest_memfdpages prior to launch. It was in the context of whether to keep usingkvm_gmem_populate()for populatingguest_memfdpages by copying them in from separate userspace buffer vs. simply populating them directly from userspace. I thought we were leaning on the latter since it was simpler all-around, which is great for SNP since that is already how it populates memory: by writing to it from userspace, whichkvm_gmem_populate()then copies intoguest_memfdpages. With shared gmem support, we just skip the latter now in the kernel rather needing changes to how userspace handles things in that regard. But maybe that was just wishful thinking :)You remember it correctly and that's how userspace should pre-populate guest memory contents with in-place conversion support available. Userspace can simply do the following scheme as an example:
1) Createguest_memfdwith theINIT_PRIVATEflag or if we decide to not go that way, create aguest_memfdfile and set all ranges as private.
2) Preallocate theguest_memfdranges.
3) Convert the needed ranges to shared, populate the initial guest payload and then convert those ranges back to private.
Quoted 2 lines
Important point here is that
guest_memfdranges can be marked as private before pre-allocatingguest_memfdranges.
Got it, and then the merge logic triggers so you get the 1GB back before guest launch. That seems reasonable. I was only thinking of the merge logic in the context of a running guest and it didn't seem all that useful in that regard, but it makes perfect sense for the above sort of scenario.
Thanks,
Mike
Quoted 59 lines
Quoted 17 lines
But you raise some very compelling points on why this might not be a good idea even if that was how that discussion went.
Quoted 11 lines
Will following work?
1) Userspace starts allguest_memfdranges as private.
2) During early guest boot it starts issuing PSC requests for converting memory from shared to private-> KVM forwards this request to userspace -> Userspace checks that the pages are already private and simplydoes nothing.
3) Pvalidate from guest on that memory will result inguest_memfdoffset query which will cause the RMP table entries to actually get populated.Quoted 4 lines
That would work, but there will need to be changes on userspace to deal with how SNP populates memory pre-boot just like normal VMs do. We will instead need to copy that data into separate buffers, and pass those in as the buffer hva instead of the shared hva corresponding to that GPA.
Initial guest memory payload generally carries a much smaller footprint so I ignored that detail in the above sequence. As I said above, userspace should be able to use
guest_memfdranges to directly populate contents by converting those ranges to shared.Quoted 22 lines
But that seems reasonable if it avoids so many other problems.
Quoted 19 lines
Quoted 18 lines
we end up splitting every 1GB to 4K and the guest can't even accept/PVALIDATE it 2MB at that point even if userspace doesn't touch anything in the range. As some point the guest will convert/accept the entire range, at which point we could merge, but for SNP we'd need guest cooperation to actually use a higher-granularity in stage2 page tables at that point since RMP entries are effectively all split to 4K.
I understand the intent is to default to private where this wouldn't be an issue, and we could punt to userspace to deal with it, but it feels like an artificial restriction to place on userspace. And if we do want to allow/expect
guest_memfdcontents to be initialized pre-boot just like normal memory, then userspace would need to jump through some hoops:- if defaulting to private: add hooks to convert each range that's being modified to a shared state prior to writing to it
Quoted 6 lines
Quoted 1 line
Why is that a problem?
These were only problems if we went the above-mentioned way of populating memory pre-boot via
mmap()instead of other backchannels. If we don't do that, then both these things cease to be problems. Sounds goods to me. :)I think there wouldn't be a problem even if we pre-populated memory pre-boot via
mmap(). Usingmmap()seems a preferable option to me.
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 59 lines · Michael Roth
Quoted 50 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?Quoted 6 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use
mmap()to populate memory (in which case you can pretty much ignore my previous response))I am assuming in-place conversion with huge page backing for the discussion below.
Looks like there are three scenarios/usecases we are discussing here:
1) Pre-allocatingguest_memfdfile offsets
- Userspace can use fallocate to do this for hugepages by keeping
the file ranges marked private.
2) Prefaulting guest EPT/NPT entries
3) Populating initial guest payload intoguest_memfdmemory
- Userspace can mark certain ranges as shared, populate the
contents and convert the ranges back to private. So mmap will come in handy here.Quoted 5 lines
I'm still not sure where the
INIT_PRIVATEflag comes into play. For SNP, userspace already defaults to marking everything private pretty close toguest_memfdcreation time, so the potential for allocations to occur in-between seems small, but worth confirming.Ok, I am not much worried about whether the
INIT_PRIVATEflag gets supported or not, but more about the default setting that different CVMs start with. To me, it looks like all CVMs should start as everything private by default and if there is a way to bake that configuration duringguest_memfdcreation time that would be good to have instead of doing "create and convert" operations and there is a fairly low cost to support this flag.Quoted 9 lines
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating
guest_memfdmemory (and even pre-faulting viaKVM_PRE_FAULT_MEMORY), but I think that could still work right? Thefallocate()handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for thatfallocate()to happen before userspace does the "manually"shared->privateconversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?Quoted 3 lines
This usecase of being able to preallocate should still work with in-place conversion assuming all ranges are private before pre-population.
Ok, I think I was missing that the merge logic here will then restore it to 1GB before the guest starts, so the folio isn't permanently split if we do the
mmap()and that gives us more flexibility on how we can use it.
Quoted 5 lines
I was thinking we needed to avoid the split from the start by avoiding paths like
mmap()which might trigger the split. I was trying to avoid anymerge->unsplitlogic in the THP case (or unsplit in general), in which case we'd get permanent splits via themmap()approach, but for 2MB that's probably not a big deal.
After initial payload population, during its runtime guest can cause different hugepages to get split which can remain split even after guest converts them back to private. For THP there may not be much benefit of merging those pages together specially if NPT/EPT entries can't be promoted back to hugepage mapping and there is no memory penalty as THP doesn't use HVO.
Wishful thinking on my part: It would be great to figure out a way to promote these pagetable entries without relying on the guest, if possible with ABI updates, as I think the host should have some control over EPT/NPT granularities even for Confidential VMs. Along the similar lines, it would be great to have "page struct"-less memory working for Confidential VMs, which should greatly reduce the toil with merge/split operations and will render the conversions mostly to be pagetable manipulations.
That being said, memory split and merge seem to be relatively lightweight for THP (with no memory allocation/freeing) and reusing the memory files after reboot of the guest VM will require pages to be merged to start with a clean slate. One possible option is to always merge as early as possible, second option is to invent a new UAPI to do it on demand.
For 1G pages, even if we go with 1G -> 2M -> 4K split stages, page splits result in higher memory usage with HVO around and it becomes useful to merge them back as early as possible as guest proceeds to convert subranges of different hugepages over its lifetime. Merging pages as early as possible also allows reusing of memory files during the next reboot without having to invent a new UAPI.
Caveats with "merge as early as possible":
- Shared to private conversions will be slower for hugetlb pages.
* Counter argument: These conversions are already slow as we need
safe refcounts to reach on the ranges getting converted.
- If guests convert a particular range often then extra merge/split operations will result in overhead.
* Counter argument: Since conversions are anyways slow, it's
beneficial for guests to avoid such a scenario and keep back and forth conversions as less frequent as possible.
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 73 lines · Vishal Annapurve
Quoted 59 lines · Michael Roth
Quoted 50 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Quoted 6 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?Quoted 6 lines
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use
mmap()to populate memory (in which case you can pretty much ignore my previous response))I am assuming in-place conversion with huge page backing for the discussion below.
Looks like there are three scenarios/usecases we are discussing here:
1) Pre-allocatingguest_memfdfile offsets
- Userspace can use fallocate to do this for hugepages by keeping
the file ranges marked private.
2) Prefaulting guest EPT/NPT entries
3) Populating initial guest payload intoguest_memfdmemory
- Userspace can mark certain ranges as shared, populate the
contents and convert the ranges back to private. So mmap will come in handy here.Quoted 5 lines
I'm still not sure where the
INIT_PRIVATEflag comes into play. For SNP, userspace already defaults to marking everything private pretty close toguest_memfdcreation time, so the potential for allocations to occur in-between seems small, but worth confirming.Ok, I am not much worried about whether the
INIT_PRIVATEflag gets supported or not, but more about the default setting that different CVMs start with. To me, it looks like all CVMs should start as everything private by default and if there is a way to bake that configuration duringguest_memfdcreation time that would be good to have instead of doing "create and convert" operations and there is a fairly low cost to support this flag.Quoted 9 lines
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating
guest_memfdmemory (and even pre-faulting viaKVM_PRE_FAULT_MEMORY), but I think that could still work right? Thefallocate()handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for thatfallocate()to happen before userspace does the "manually"shared->privateconversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?Quoted 3 lines
This usecase of being able to preallocate should still work with in-place conversion assuming all ranges are private before pre-population.
Ok, I think I was missing that the merge logic here will then restore it to 1GB before the guest starts, so the folio isn't permanently split if we do the
mmap()and that gives us more flexibility on how we can use it.Quoted 5 lines
I was thinking we needed to avoid the split from the start by avoiding paths like
mmap()which might trigger the split. I was trying to avoid anymerge->unsplitlogic in the THP case (or unsplit in general), in which case we'd get permanent splits via themmap()approach, but for 2MB that's probably not a big deal.After initial payload population, during its runtime guest can cause different hugepages to get split which can remain split even after guest converts them back to private. For THP there may not be much benefit of merging those pages together specially if NPT/EPT entries can't be promoted back to hugepage mapping and there is no memory penalty as THP doesn't use HVO.
Quoted 4 lines
Wishful thinking on my part: It would be great to figure out a way to promote these pagetable entries without relying on the guest, if possible with ABI updates, as I think the host should have some control over EPT/NPT granularities even for Confidential VMs. Along
I'm not sure how much it would buy us. For example, for a 2MB hugetlb SNP guest boot with 16GB of memory I see 622 2MB hugepages getting split, but only about 30 or so of those get merged back to 2MB folios during guest run-time. These are presumably the set of 2MB regions we could promote back up, but it's not much given that we wouldn't expect that value to grow proportionally for larger guests: it's really separate things like the number of vCPUs (for shared GHCB pages), number of virtio buffers, etc. that end up determining the upper bound on how many pages might get split due to 4K private->shared conversion, and these would vary all that much from get to get outside maybe vCPU count.
For 1GB hugetlb I see about 6 1GB pages get split, and only 2 get merged during run-time and would be candidates for promotion.
This could be greatly improved from the guest side by using higher-order allocations to create pools of shared memory that could then be used to reduce the number of splits caused by doing private->shared conversions on random ranges of malloc'd memory, and this could be done even without special promotion support on the host for pretty much the entirety of guest memory. The idea there would be to just making optimized guests avoid the splits completely, rather than relying on the limited subset that hardware can optimize without guest cooperation.
Quoted 4 lines
the similar lines, it would be great to have "page struct"-less memory working for Confidential VMs, which should greatly reduce the toil with merge/split operations and will render the conversions mostly to be pagetable manipulations.
FWIW, I did some profiling of split/merge vs. overall conversion time (by that I mean all cycles spent within kvm_gmem_convert_execute_work()), and while split/merge does take quite a few more cycles than your average conversion operation (~100x more), the total cycles spent splitting/merging ended up being about 7% of the total cycles spent handling conversions (1043938460 cycles in this case).
For 1GB, a split/merge take >1000x more than a normal conversion operation (46475980 cycles vs 320 in this sample), but it's probably still not too bad vs the overall conversion path, and as mentioned above it only happens about 6x for 16GB SNP guest so I don't think split/merge overhead is a huge deal for current guests, especially if we work toward optimizing guest-side usage of shared memory in the future. (There is potential for this to crater performance for a very poorly-optimized guest however but I think the guest should bear some burden for that sort of thing: e.g. flipping the same page back-and-forth between shared/private vs. caching it for continued usage as shared page in the guest driver path isn't something we should put too much effort into optimizing.)
Quoted 15 lines
That being said, memory split and merge seem to be relatively lightweight for THP (with no memory allocation/freeing) and reusing the memory files after reboot of the guest VM will require pages to be merged to start with a clean slate. One possible option is to always merge as early as possible, second option is to invent a new UAPI to do it on demand.
For 1G pages, even if we go with 1G -> 2M -> 4K split stages, page splits result in higher memory usage with HVO around and it becomes useful to merge them back as early as possible as guest proceeds to convert subranges of different hugepages over its lifetime. Merging pages as early as possible also allows reusing of memory files during the next reboot without having to invent a new UAPI.
Quoted 9 lines
Caveats with "merge as early as possible":
- Shared to private conversions will be slower for hugetlb pages.
* Counter argument: These conversions are already slow as we need
safe refcounts to reach on the ranges getting converted.
- If guests convert a particular range often then extra merge/split operations will result in overhead.
* Counter argument: Since conversions are anyways slow, it's
beneficial for guests to avoid such a scenario and keep back and forth conversions as less frequent as possible.
Fair enough. I'm not seeing any major reason not to do things this way, as the overhead doesn't seem to be very significant for the common case.
(even though, as noted above, the amount of hugetlb pages we actually end up merging at guest run-time seems to be fairly small, but maybe there are scenarios where this will have a bigger impact, and it certainly helps to have it there for the pre-boot merges.)
-Mike
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 18 lines · Michael Roth
Quoted 5 lines
Wishful thinking on my part: It would be great to figure out a way to promote these pagetable entries without relying on the guest, if possible with ABI updates, as I think the host should have some control over EPT/NPT granularities even for Confidential VMs. Along
I'm not sure how much it would buy us. For example, for a 2MB hugetlb SNP guest boot with 16GB of memory I see 622 2MB hugepages getting split, but only about 30 or so of those get merged back to 2MB folios during guest run-time. These are presumably the set of 2MB regions we could promote back up, but it's not much given that we wouldn't expect that value to grow proportionally for larger guests: it's really separate things like the number of vCPUs (for shared GHCB pages), number of virtio buffers, etc. that end up determining the upper bound on how many pages might get split due to 4K
private->sharedconversion, and these would vary all that much from get to get outside maybe vCPU count.
Quoted 3 lines
For 1GB hugetlb I see about 6 1GB pages get split, and only 2 get merged during run-time and would be candidates for promotion.
Thanks for the great analysis here. I think we will need to repeat such analysis for other scenarios such as usage with accelerators.
Quoted 9 lines
This could be greatly improved from the guest side by using higher-order allocations to create pools of shared memory that could then be used to reduce the number of splits caused by doing
private->sharedconversions on random ranges of malloc'd memory, and this could be done even without special promotion support on the host for pretty much the entirety of guest memory. The idea there would be to just making optimized guests avoid the splits completely, rather than relying on the limited subset that hardware can optimize without guest cooperation.
Yes, it would be great to improve the situation from the guest side, e.g. I tried with a rough draft [1], the conclusion there was that we need to set aside "enough" guest memory as CMA to cause all the DMA go through 2M aligned buffers. It's hard to figure out how much is "enough", but we could start somewhere. That being said, the host still has to manage memory this way by splitting/merging at runtime because I don't think it's possible to enforce all conversions to happen at 2M (or any at 1G) granularity. So it's also very likely that even if guests do significant chunk of conversions at hugepage granularity, host still needs to split pages all the way to 4K for all shared regions unless we can bake another restriction in the conversion ABI that guests can only convert the same ranges to private as were converted before to shared.
[1] https://lore.kernel.org/lkml/[email protected]/
Quoted 13 lines
Quoted 5 lines
the similar lines, it would be great to have "page struct"-less memory working for Confidential VMs, which should greatly reduce the toil with merge/split operations and will render the conversions mostly to be pagetable manipulations.
FWIW, I did some profiling of split/merge vs. overall conversion time (by that I mean all cycles spent within
kvm_gmem_convert_execute_work()), and while split/merge does take quite a few more cycles than your average conversion operation (~100x more), the total cycles spent splitting/merging ended up being about 7% of the total cycles spent handling conversions (1043938460 cycles in this case).
Quoted 13 lines
For 1GB, a split/merge take >1000x more than a normal conversion operation (46475980 cycles vs 320 in this sample), but it's probably still not too bad vs the overall conversion path, and as mentioned above it only happens about 6x for 16GB SNP guest so I don't think split/merge overhead is a huge deal for current guests, especially if we work toward optimizing guest-side usage of shared memory in the future. (There is potential for this to crater performance for a very poorly-optimized guest however but I think the guest should bear some burden for that sort of thing: e.g. flipping the same page back-and-forth between shared/private vs. caching it for continued usage as shared page in the guest driver path isn't something we should put too much effort into optimizing.)
As per discussions in the past, guest_memfd private pages are simply only managed by guest_memfd. We don't need and effectively don't want the kernel to manage guest private memory. So effectively we can get rid of page structs in theory just for private pages as well and allocate page structs only for shared memory on conversion and deallocate on conversion back to private.
And when we have base core-mm allocators that hand out raw pfns to start with, we don't even need shared memory ranges to be backed by page structs.
Few hurdles we need to cross:
1) Invent a new filemap equivalent that maps guest_memfd offsets to pfns
2) Modify TDX EPT management to work with pfns and not page structs
3) Modify generic KVM NPT/EPT management logic to work with pfns and not rely on page structs
4) Modify memory error/hwpoison handling to route all memory errors on such pfns to guest_memfd.
I believe there are obvious benefits (reduced complexity, reduced memory footprint etc) if we go this route and we are very likely to go this route for future usecases even if we decide to live with conversion costs today.
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Michael RothHi,
Quoted 90 lines · Michael Roth
Quoted 71 lines · Vishal Annapurve
Quoted 70 lines · Michael Roth
Quoted 68 lines · Ackerley Tng
Quoted 41 lines · Michael Roth
Quoted 40 lines · Ackerley Tng
Track
guest_memfdmemory's shareability status within the inode as opposed to the file, since it is property of the guest_memfd's memory contents.Shareability is a property of the memory and is indexed using the page's index in the inode. Because shareability is the memory's property, it is stored within
guest_memfdinstead of within KVM, like inkvm->mem_attr_array.
KVM_MEMORY_ATTRIBUTE_PRIVATEinkvm->mem_attr_arraymust still be retained to allow VMs to only useguest_memfdfor private memory and some other memory for shared memory.Not all use cases require
guest_memfd()to be shared with the host when first created. Add a new flag,GUEST_MEMFD_FLAG_INIT_PRIVATE, which when set onKVM_CREATE_GUEST_MEMFD, initializes the memory as private to the guest, and therefore not mappable by the host. Otherwise, memory is shared until explicitly converted to private.Signed-off-by Ackerley Tng <[email protected]>Co-developed-by Vishal Annapurve <[email protected]>Signed-off-by Vishal Annapurve <[email protected]>Co-developed-by Fuad Tabba <[email protected]>Signed-off-by Fuad Tabba <[email protected]>Change-Id If03609cbab3ad1564685c85bdba6dcbb6b240c0f--- Documentation/virt/kvm/api.rst | 5 ++ include/uapi/linux/kvm.h | 2 + virt/kvm/guest_memfd.c | 124 ++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-)Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.Quoted 11 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6415
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host. +
KVM_CAP_GMEM_CONVERSIONdoesn't get introduced until later, so it seems like this flag should be deferred until that patch is in place. Is it really needed at that point though? Userspace would be able to set the initial state via KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls.I can move this change to the later patch. Thanks! Will fix in the next revision.
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use
mmap()to populate memory (in which case you can pretty much ignore my previous response))I'm still not sure where the
INIT_PRIVATEflag comes into play. For SNP, userspace already defaults to marking everything private pretty close toguest_memfdcreation time, so the potential for allocations to occur in-between seems small, but worth confirming.
Quoted 8 lines
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating
guest_memfdmemory (and even pre-faulting viaKVM_PRE_FAULT_MEMORY), but I think that could still work right? Thefallocate()handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for thatfallocate()to happen before userspace does the "manually"shared->privateconversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?
It's not just about performance. I think that the need is more a matter of having a consistent API with the hypervisors guest_memfd is going to support. Memory in guest_memfd is shared by default, but in pKVM for example, it's private by default. Therefore, it would be good to have a way to ensure that all guest_memfd allocations can be made private from the get-go.
Cheers,
/fuad
Quoted 482 lines
Quoted 19 lines
Quoted 19 lines
I assume we do want to support things like preallocating guest memory so not sure this approach is feasible to avoid splits.
But I feel like we might be working around a deeper issue here, which is that we are pre-emptively splitting anything that *could* be mapped into userspace (i.e. allocated+shared/mixed), rather than splitting when necessary.
I know that was the plan laid out in the
guest_memfdcalls, but I've run into a couple instances that have me thinking we should revisit this.1) Some of the recent
guest_memfdseems to be gravitating towards having userspace populate/initialize guest memory payload prior to boot via mmap()'ing the sharedguest_memfdpages so things work the same as they would for initialized normal VM memory payload (rather than relying on back-channels in the kernel to user data intoguest_memfdpages).Quoted 8 lines
Quoted 3 lines
When you do this though, for an SNP guest at least, that memory acceptance is done in chunks of 4MB (with accept_memory=lazy), and because that will put each 1GB page into an allocated+mixed state,
I would like your help in understanding why we need to start
guest_memfdranges as shared for SNP guests.guest_memfdranges being private simply should mean that certain ranges are not faultable by the userspace.It's seeming like I probably misremembered, but I thought there was a discussion on
guest_memfdcall a month (or so?) ago about whether to continue to use backchannels to populateguest_memfdpages prior to launch. It was in the context of whether to keep usingkvm_gmem_populate()for populatingguest_memfdpages by copying them in from separate userspace buffer vs. simply populating them directly from userspace. I thought we were leaning on the latter since it was simpler all-around, which is great for SNP since that is already how it populates memory: by writing to it from userspace, whichkvm_gmem_populate()then copies intoguest_memfdpages. With shared gmem support, we just skip the latter now in the kernel rather needing changes to how userspace handles things in that regard. But maybe that was just wishful thinking :)But you raise some very compelling points on why this might not be a good idea even if that was how that discussion went.
Quoted 11 lines
Will following work?
1) Userspace starts allguest_memfdranges as private.
2) During early guest boot it starts issuing PSC requests for converting memory from shared to private-> KVM forwards this request to userspace -> Userspace checks that the pages are already private and simplydoes nothing.
3) Pvalidate from guest on that memory will result inguest_memfdoffset query which will cause the RMP table entries to actually get populated.That would work, but there will need to be changes on userspace to deal with how SNP populates memory pre-boot just like normal VMs do. We will instead need to copy that data into separate buffers, and pass those in as the buffer hva instead of the shared hva corresponding to that GPA.
But that seems reasonable if it avoids so many other problems.
Quoted 16 lines
Quoted 16 lines
we end up splitting every 1GB to 4K and the guest can't even accept/PVALIDATE it 2MB at that point even if userspace doesn't touch anything in the range. As some point the guest will convert/accept the entire range, at which point we could merge, but for SNP we'd need guest cooperation to actually use a higher-granularity in stage2 page tables at that point since RMP entries are effectively all split to 4K.
I understand the intent is to default to private where this wouldn't be an issue, and we could punt to userspace to deal with it, but it feels like an artificial restriction to place on userspace. And if we do want to allow/expect
guest_memfdcontents to be initialized pre-boot just like normal memory, then userspace would need to jump through some hoops:Quoted 4 lines
Quoted 2 lines
- if defaulting to private: add hooks to convert each range that's being modified to a shared state prior to writing to it
Why is that a problem?
These were only problems if we went the above-mentioned way of populating memory pre-boot via
mmap()instead of other backchannels. If we don't do that, then both these things cease to be problems. Sounds goods to me. :)Quoted 11 lines
Quoted 8 lines
- if defaulting to shared: initialize memory in-place, then covert everything else to private to avoid unecessarily splitting folios at run-time
It feels like implementations details are bleeding out into the API to some degree here (e.g. we'd probably at least need to document this so users know how to take proper advantage of hugepage support).
Does it make sense to keep the default behavior as
INIT_PRIVATEfor SNP VMs always even without using hugepages?Yes!
Though, revisiting discussion around
INIT_PRIVATE(without the baggage of potentially relying onmmap()to populate memory), I'm still not sure why it's needed. I responded in the context of Ackerley's initial reply above.Quoted 15 lines
Quoted 9 lines
2) There are some use-cases for HugeTLB + CoCo that have come to my attention recently that put a lot of weight on still being able to maximize mapping/hugepage size when accessing shared mem from userspace, e.g. for certain DPDK workloads that accessed shared guest buffers from host userspace. We don't really have a story for this, and I wouldn't expect us to at this stage, but I think it ties into #1 so might be worth considering in that context.
Major problem I see here is that if anything in the kernel does a GUP on shared memory ranges (which is very likely to happen), it would be difficult to get them to let go of the whole hugepage before it can be split safely.
Quoted 4 lines
Another problem is
guest_memfdtoday doesn't support management of large user space page table mappings, this can turnout to be significant work to do referring to hugetlb pagetable management logic.Yah that was more line-of-sight that might be possible by going this route, but the refcount'ing issue above is a showstopper as always. I'd somehow convinced myself that supporting fine-grained splitting somehow worked around it, but you still have no idea what page you need to avoid converting and fancy splitting doesn't get you past that. More wishful thinking. =\
Thanks,
Mike
Quoted 343 lines
Quoted 343 lines
I'm still fine with the current approach as a starting point, but I'm wondering if improving both #1/#2 might not be so bad and maybe even give us some more flexibility (for instance, Sean had mentioned leaving open the option of tracking more than just shareability/mappability, and if there is split/merge logic associated with those transitions then re-scanning each of these attributes for a 1G range seems like it could benefit from some sort of intermediate data structure to help determine things like what mapping granularity is available for guest/userspace for a particular range.
One approach I was thinking of was that we introduce a data structure similar to KVM's
memslot->arch.lpage_info()where we store information about what 1G/2M ranges are shared/private/mixed, and then instead of splitting ahead of time we just record that state into this data structure (using the same write lock as with the shareability/mappability state), and then at *fault* time we split the folio if our lpage_info-like data structure says the range is mixed.Then, if guest converts a 2M/4M range to private while lazilly-accepting (for instance), we can still keep the folio intact as 1GB, but mark the 1G range in the lpage_info-like data structure as mixed so that we still inform KVM/etc. they need to map it as 2MB or lower in stage2 page tables. In that case, even at guest fault-time, we can leave the folio unsplit until userspace tries to touch it (though in most cases it never will and we can keep most of the guest's 1G intact for the duration of its lifetime).
On the userspace side, another nice thing there is if we see 1G is in a mixed state, but 2M is all-shared, then we can still leave the folio as 2M, and I think the refcount'ing logic would still work for the most part, which makes #2 a bit easier to implement as well.
And of course, we wouldn't need the
INIT_PRIVATEthen since we are only splitting when necessary.But I guess this all comes down to how much extra pain there is in tracking a 1G folio that's been split into a mixed of 2MB/4K regions, but I think we'd get a lot more mileage out of getting that working and just completely stripping out all of the merging logic for initial implementation (other than at cleanup time), so maybe complexity-wise it balances out a bit?
Thanks,
Mike
Quoted 295 lines
Quoted 294 lines
Quoted 62 lines
Documentation/virt/kvm/api.rst +0
-0See KVM_SET_USER_MEMORY_REGION2 for additional details. 4.143 KVM_PRE_FAULT_MEMORYinclude/uapi/linux/kvm.h +2
-0@@ -1567,7 +1567,9 @@ struct kvm_memory_attributes {#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd) +#define GUEST_MEMFD_FLAG_SUPPORT_SHARED (1UL << 0) +#define GUEST_MEMFD_FLAG_INIT_PRIVATE (1UL << 1)struct kvm_create_guest_memfd {__u64 size;virt/kvm/guest_memfd.c +22
-0@@ -4,6 +4,7 @@#include <linux/falloc.h> #include <linux/fs.h> #include <linux/kvm_host.h> +#include <linux/maple_tree.h> #include <linux/pseudo_fs.h> #include <linux/pagemap.h> @@ -17,6 +18,24 @@ struct kvm_gmem {struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +struct maple_tree shareability; +#endif +}; + +enum shareability { +SHAREABILITY_GUEST =1 ,/* Only the guest can map (fault) folios in this range. */ +SHAREABILITY_ALL =2 ,/* Both guest and host can fault folios in this range. */ +}; + +static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) +{ +return inode->i_mapping->i_private_data; +} +/** * folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index. @@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index)return folio_pfn (folio) + (index & (folio_nr_pages (folio) -1 )); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private,virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+ loff_t size, u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via
KVM_SET_MEMORY_ATTRIBUTES(which is the current QEMU default behavior).I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
Maybe it would make sense for KVM memory attributes too?
Quoted 135 lines
virt/kvm/guest_memfd.c +87
-1+} + +static enum shareability kvm_gmem_shareability_get (struct inode *inode, +pgoff_t index) +{ +struct maple_tree *mt; +void *entry; + + mt = &kvm_gmem_private (inode)->shareability; + entry =mtree_load (mt, index); +WARN (!entry, +"Shareability should always be defined for all indices in inode." ); + +return xa_to_value (entry); +} + +static struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ +if (kvm_gmem_shareability_get (inode, index) !=SHAREABILITY_ALL ) +return ERR_PTR (-EACCES ); + +return kvm_gmem_get_folio (inode, index); +} + +#else + +static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags) +{ +return 0 ; +} + +static inline struct folio *kvm_gmem_get_shared_folio (struct inode *inode,pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") +return NULL ; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static int __kvm_gmem_prepare_folio (struct kvm *kvm,struct kvm_memory_slot *slot,pgoff_t index,struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)filemap_invalidate_lock_shared (inode->i_mapping); - folio =kvm_gmem_get_folio (inode, vmf->pgoff); + folio =kvm_gmem_get_shared_folio (inode, vmf->pgoff);if (IS_ERR (folio)) {int err =PTR_ERR (folio);@@ -420,8 +491,33 @@ static struct file_operations kvm_gmem_fops = { .fallocate = kvm_gmem_fallocate, }; +static void kvm_gmem_free_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +kfree (private); + +free_inode_nonrcu (inode); +} + +static void kvm_gmem_destroy_inode (struct inode *inode) +{ +struct kvm_gmem_inode_private *private =kvm_gmem_private (inode); + +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +/* +* mtree_destroy() can't be used within rcu callback, hence can't be +* done in ->free_inode(). +*/ +if (private) +mtree_destroy (&private->shareability); +#endif +} +static const struct super_operations kvm_gmem_super_operations = { .statfs = simple_statfs, + .destroy_inode = kvm_gmem_destroy_inode, + .free_inode = kvm_gmem_free_inode, };static int kvm_gmem_init_fs_context (struct fs_context *fc)@@ -549,12 +645,26 @@ static const struct inode_operations kvm_gmem_iops = {static struct inode *kvm_gmem_inode_make_secure_inode (const char *name,loff_t size,u64 flags) { +struct kvm_gmem_inode_private *private;struct inode *inode; +int err; inode =alloc_anon_secure_inode (kvm_gmem_mnt->mnt_sb, name);if (IS_ERR (inode))return inode; + err = -ENOMEM ; + private =kzalloc (sizeof (*private),GFP_KERNEL ); +if (!private) + goto out; + +mt_init (&private->shareability); + inode->i_mapping->i_private_data = private; + + err =kvm_gmem_shareability_setup (private, size, flags); +if (err) + goto out; + inode->i_private = (void *)(unsigned long )flags; inode->i_op = &kvm_gmem_iops; inode->i_mapping->a_ops = &kvm_gmem_aops;@@ -566,6 +676,11 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,WARN_ON_ONCE (!mapping_unevictable (inode->i_mapping));return inode; + +out: +iput (inode); + +return ERR_PTR (err); } static struct file *kvm_gmem_inode_create_getfile(void *priv, loff_t size,@@ -654,6 +769,9 @@ int kvm_gmem_create(struct kvm *kvm, struct kvm_create_guest_memfd *args)if (kvm_arch_vm_supports_gmem_shared_mem (kvm)) valid_flags |=GUEST_MEMFD_FLAG_SUPPORT_SHARED ; +if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) + valid_flags |=GUEST_MEMFD_FLAG_INIT_PRIVATE ; +if (flags & ~valid_flags)return -EINVAL ;virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 5 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new
SHAREABILITY_GUEST_PREPAREDstate) theSHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPAREDtransition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.I was originally planning on introducing a new
rw_semaphorewith similar semantics to therw_lockthat Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle settingSHAREABILITY_GUEST_PREPAREDwithin fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodesbut figured I'd mention it here in case there are other things to consider on the locking front.
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/Quoted 16 lines
virt/kvm/guest_memfd.c +1
-2folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; } --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 11 lines · Fuad Tabba
Hi,
Quoted 8 lines · Michael Roth
Quoted 6 lines · Vishal Annapurve
Quoted 5 lines · Michael Roth
Quoted 3 lines · Ackerley Tng
Quoted 1 line · Michael Roth
On Wed, May 14, 2025 at 04:41:41PM -0700, Ackerley Tng wrote:
[snip]
Quoted 36 lines
Quoted 27 lines
Quoted 10 lines
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines
The mtree contents seems to get stored in the same manner in either case so performance-wise only the overhead of a few userspace<->kernel switches would be saved. Are there any other reasons?
Otherwise, maybe just settle on SHARED as a documented default (since at least non-CoCo VMs would be able to reliably benefit) and let CoCo/GUEST_MEMFD_FLAG_SUPPORT_SHARED VMs set PRIVATE at whatever granularity makes sense for the architecture/guest configuration.
Quoted 7 lines
Quoted 7 lines
Quoted 7 lines
Because shared pages are split once any memory is allocated, having a way to
INIT_PRIVATEcould avoid the split and then merge on conversion. I feel that is enough value to have this config flag, what do you think?I guess we could also have userspace be careful not to do any allocation before converting.
(Re-visiting this with the assumption that we *don't* intend to use
mmap()to populate memory (in which case you can pretty much ignore my previous response))I'm still not sure where the
INIT_PRIVATEflag comes into play. For SNP, userspace already defaults to marking everything private pretty close toguest_memfdcreation time, so the potential for allocations to occur in-between seems small, but worth confirming.Quoted 8 lines
But I know in the past there was a desire to ensure TDX/SNP could support pre-allocating
guest_memfdmemory (and even pre-faulting viaKVM_PRE_FAULT_MEMORY), but I think that could still work right? Thefallocate()handling could still avoid the split if the whole hugepage is private, though there is a bit more potential for thatfallocate()to happen before userspace does the "manually"shared->privateconversion. I'll double-check on that aspect, but otherwise, is there still any other need for it?
Quoted 4 lines
It's not just about performance. I think that the need is more a matter of having a consistent API with the hypervisors
guest_memfdis going to support. Memory inguest_memfdis shared by default, but in pKVM for example, it's private by default. Therefore, it would be good
^^^^^^^^^^^^^^^^
And Coco's as well right?
Ira
Quoted 6 lines
to have a way to ensure that all
guest_memfdallocations can be made private from the get-go.Cheers,
/fuad
[snip]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Michael RothQuoted 1 line · Michael Roth
On Wed, May 14, 2025 at 04:41:41PM -0700, Ackerley Tng wrote:
Missed out responses on the second two comments!
[...]
Quoted 19 lines
virt/kvm/guest_memfd.c static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 63
Quoted 13 lines
virt/kvm/guest_memfd.c +13
-0+#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup (struct kvm_gmem_inode_private *private, +loff_t size,u64 flags) +{ +enum shareability m; +pgoff_t last; + + last = (size >>PAGE_SHIFT ) -1 ; + m = flags &GUEST_MEMFD_FLAG_INIT_PRIVATE ?SHAREABILITY_GUEST : +SHAREABILITY_ALL ; +return mtree_store_range (&private->shareability,0 , last,xa_mk_value (m), +GFP_KERNEL );One really nice thing about using a maple tree is that it should get rid of a fairly significant startup delay for SNP/TDX when the entire xarray gets initialized with private attribute entries via
KVM_SET_MEMORY_ATTRIBUTES(which is the current QEMU default behavior).
Quoted 4 lines
I'd originally advocated for sticking with the xarray implementation Fuad was using until we'd determined we really need it for HugeTLB support, but I'm sort of thinking it's already justified just based on the above.
We discussed this at the guest_memfd upstream call, and I believe the current position is to go with maple_trees. Thanks for bringing this up!
Quoted 2 lines
Maybe it would make sense for KVM memory attributes too?
I think so, but I haven't had the chance to work on that.
Quoted 2 lines
Quoted 2 lines
(quoted diff) +2
-0+} +
[...]
Quoted 6 lines
virt/kvm/guest_memfd.c int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, · line 964
Quoted 6 lines
virt/kvm/guest_memfd.c +2
-0@@ -842,6 +960,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,if (!file)return -EFAULT ; +filemap_invalidate_lock_shared (file_inode (file)->i_mapping); +
In this RFC, the filemap_invalidate_lock() was basically used to serialize everything that could modify shareability.
Quoted 41 lines
I like the idea of using a write-lock/read-lock to protect write/read access to shareability state (though maybe not necessarily re-using filemap's invalidate lock), it's simple and still allows concurrent faulting in of gmem pages. One issue on the SNP side (which also came up in one of the gmem calls) is if we introduce support for tracking preparedness as discussed (e.g. via a new
SHAREABILITY_GUEST_PREPAREDstate) theSHAREABILITY_GUEST->SHAREABILITY_GUEST_PREPAREDtransition would occur at fault-time, and so would need to take the write-lock and no longer allow for concurrent fault-handling.I was originally planning on introducing a new
rw_semaphorewith similar semantics to therw_lockthat Fuad previously had in his restricted mmap series[1] (and simiar semantics to filemap invalidate lock here). The main difference, to handle settingSHAREABILITY_GUEST_PREPAREDwithin fault paths, was that in the case of a folio being present for an index, the folio lock would also need to be held in order to update the shareability state. Because of that, fault paths (which will always either have or allocate folio basically) can rely on the folio lock to guard shareability state in a more granular way and so can avoid a global write lock.They would still need to hold the read lock to access the tree however. Or more specifically, any paths that could allocate a folio need to take a read lock so there isn't a TOCTOU situation where shareability is being updated for an index for which a folio hasn't been allocated, but then just afterward the folio gets faulted in/allocated while the shareability state is already being updated which the understand that there was no folio around that needed locking.
I had a branch with in-place conversion support for SNP[2] that added this lock reworking on top of Fuad's series along with preparation tracking, but I'm now planning to rebase that on top of the patches from this series that Sean mentioned[3] earlier:
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodes
Quoted 2 lines
but figured I'd mention it here in case there are other things to consider on the locking front.
We discussed this a little at the last guest_memfd call: I'll summarize the question I raised during the call here in text. :)
Today in guest_memfd the "prepared" and "zeroed" concepts are tracked with the folio's uptodate flag.
Preparation is only used by SNP today and TDX does the somewhat equivalent "preparation" at time of mapping into the guest page table.
Can we do SNP's preparation at some other point in time and not let the "prepared" state be handled by guest_memfd at all?
This might simplify locking too, so preparedness would be locked whenever SNP needs to, independently of shareability tracking.
Also, this might simplify the routines that use kvm_gmem_populate(), perhaps remove the need for kvm_gmem_populate()? The current callers are basically using kvm_gmem_populate() to allocate pages, why not call kvm_gmem_get_folio() to do the allocation?
Another tangential point: it's hard to use the uptodate flag for tracking preparedness, since when there are huge pages, the uptodate flag can only indicate if the entire folio is prepared, but a user of the memory might only have part of the folio prepared.
Quoted 27 lines
Definitely agree with Sean though that it would be nice to start identifying a common base of patches for the in-place conversion enablement for SNP, TDX, and pKVM so the APIs/interfaces for hugepages can be handled separately.
-Mike
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://github.com/mdroth/linux/commits/mmap-swprot-v10-snp0-wip2/
[3] https://lore.kernel.org/kvm/[email protected]/Quoted 16 lines
virt/kvm/guest_memfd.c +1
-1folio =__kvm_gmem_get_pfn (file, slot, index, pfn, &is_prepared, max_order);if (IS_ERR (folio)) { r =PTR_ERR (folio);@@ -857,8 +977,8 @@ int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot, *page =folio_file_page (folio, index); elsefolio_put (folio ); - out: +filemap_invalidate_unlock_shared (file_inode (file)->i_mapping);fput (file);return r; }Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngQuoted 4 lines · Ackerley Tng
(quoted diff) +4
-0+static enum shareability kvm_gmem_shareability_get(struct inode *inode, + pgoff_t index) +{ + struct maple_tree *mt;
Review
Quoted 10 lines
(quoted diff) +10
-0+ void *entry; + + mt = &kvm_gmem_private(inode)->shareability; + entry = mtree_load(mt, index); + WARN(!entry, + "Shareability should always be defined for all indices in inode."); + + return xa_to_value(entry); +} +
Hi Ackerley,
Not sure if it's a known issue. Just want to let you know in case you're unaware.
During a test to repeatedly launching/destroying TDs, I encountered a warning from kvm_gmem_shareability_get() (see the attached log at the bottom). The reproducing rate is 1 in every 20-100 times of launching TD.
After some analysis, I found that the warning was produced by kvm_gmem_shareability_get() when it's called from kvm_gmem_is_private(), which is not protected by any locks.
I can get rid of the warning by either fix 1 or fix 2 below. (I prefer fix 1 though :))
fix 1:
virt/kvm/guest_memfd.c +1 -1
@@ -2024,7 +2024,7 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,#ifdef CONFIG_KVM_GMEM_SHARED_MEM if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { -mt_init (&private->shareability); +mt_init_flags (&private->shareability,MT_FLAGS_USE_RCU ); err =kvm_gmem_shareability_setup (private, size, flags);if (err)
fix 2:
virt/kvm/guest_memfd.c +2 -0
@@ -171,7 +171,9 @@ static enum shareability kvm_gmem_shareability_get(struct inode *inode,void *entry; mt = &kvm_gmem_private (inode)->shareability; +mtree_lock (mt); entry =mtree_load (mt, index); +mtree_unlock (mt);WARN (!entry,"Shareability should always be defined for all indices in inode." );
Thanks
Yan
[ 845.253021] ------------[ cut here ]------------ [ 845.259236] Shareability should always be defined for all indices in inode. [ 845.259273] WARNING: CPU: 148 PID: 3775 at arch/x86/kvm/../../../virt/kvm/guest_memfd.c:175 kvm_gmem_shareability_get.isra.0+0x39/0x50 [kvm] [ 845.283330] Modules linked in: kvm_intel i2c_i801 idxd i2c_smbus i2c_ismt kvm irqbypass nls_iso8859_1 nls_cp437 squashfs ghash_clmulni_intel hid_generic aesni_intel [ 845.300914] CPU: 148 UID: 0 PID: 3775 Comm: qemu-system-x86 Tainted: G S 6.16.0-rc6-upstream+ #520 PREEMPT(voluntary) 49e4d0c13b52dd8fe7006bbbb80b018c4576ab2d [ 845.319631] Tainted: [S]=CPU_OUT_OF_SPEC [ 845.324956] Hardware name: Intel Corporation ArcherCity/ArcherCity, BIOS EGSDCRB1.SYS.0101.D29.2303301937 03/30/2023 [ 845.337749] RIP: 0010:kvm_gmem_shareability_get.isra.0+0x39/0x50 [kvm] [ 845.346085] Code: bf 48 02 00 00 e8 a7 d4 08 d1 48 85 c0 74 09 c9 48 d1 e8 c3 cc cc cc cc 48 89 45 f8 90 48 c7 c7 a0 56 5c c0 e8 68 3c b5 cf 90 <0f> 0b 90 90 48 8b 45 f8 c9 48 d1 e8 c3 cc cc cc cc 66 0f 1f 44 00 [ 845.368227] RSP: 0018:ff29e9c2e336baa0 EFLAGS: 00010282 [ 845.375038] RAX: 0000000000000000 RBX: 00000000001825d4 RCX: 0000000000000000 [ 845.384020] RDX: 0000000000000002 RSI: 0000000000000001 RDI: 00000000ffffffff [ 845.392966] RBP: ff29e9c2e336baa8 R08: 0000000000000000 R09: 0000000000000000 [ 845.401912] R10: 0000000000000001 R11: 0000000000000000 R12: ff1236f76e067a80 [ 845.410878] R13: ff1236f76e0ecc00 R14: 0000000000000000 R15: ff1236f783af8000 [ 845.419850] FS: 00007f8b863fc6c0(0000) GS:ff12370458883000(0000) knlGS:0000000000000000 [ 845.429915] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 845.437304] CR2: 0000000000000000 CR3: 00000003e9989005 CR4: 0000000000773ef0 [ 845.446265] PKRU: 55555554 [ 845.450224] Call Trace: [ 845.453887] <TASK> [ 845.457161] kvm_gmem_is_private+0x4b/0x70 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.467348] kvm_mmu_faultin_pfn+0x14a/0x360 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.477740] kvm_tdp_page_fault+0x97/0xf0 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.487843] kvm_mmu_do_page_fault+0x23d/0x290 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.505524] ? __this_cpu_preempt_check+0x13/0x20 [ 845.515349] kvm_mmu_page_fault+0x8c/0x3d0 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.529136] tdx_handle_ept_violation+0x16a/0x310 [kvm_intel 1efe846cc4054cc289d319f1912cf040ec0ca0e6] [ 845.547760] tdx_handle_exit+0x44f/0x540 [kvm_intel 1efe846cc4054cc289d319f1912cf040ec0ca0e6] [ 845.565647] ? lock_acquire+0x52/0x70 [ 845.574284] ? vcpu_enter_guest+0x452/0x11d0 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.591886] vt_handle_exit+0x25/0x30 [kvm_intel 1efe846cc4054cc289d319f1912cf040ec0ca0e6] [ 845.609407] vcpu_enter_guest+0x4b1/0x11d0 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.623253] ? kvm_apic_local_deliver+0x8a/0xe0 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.641247] vcpu_run+0x4d/0x280 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.654096] ? vcpu_run+0x4d/0x280 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.667165] kvm_arch_vcpu_ioctl_run+0x544/0x890 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.685231] kvm_vcpu_ioctl+0x143/0x7c0 [kvm 6f655eadf3c2ae71b90b04a3d4ef5b799600c3f8] [ 845.698810] ? __fget_files+0xc2/0x1b0 [ 845.707633] ? __this_cpu_preempt_check+0x13/0x20 [ 845.717555] ? __fget_files+0xcc/0x1b0 [ 845.726405] __x64_sys_ioctl+0x9a/0xf0 [ 845.735241] ? __this_cpu_preempt_check+0x13/0x20 [ 845.745163] x64_sys_call+0x1054/0x20c0 [ 845.754043] do_syscall_64+0xc3/0x470 [ 845.762701] entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 845.772906] RIP: 0033:0x7f8d9c124ded [ 845.781398] Code: 04 25 28 00 00 00 48 89 45 c8 31 c0 48 8d 45 10 c7 45 b0 10 00 00 00 48 89 45 b8 48 8d 45 d0 48 89 45 c0 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1a 48 8b 45 c8 64 48 2b 04 25 28 00 00 00 [ 845.814651] RSP: 002b:00007f8b863f7cd0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010 [ 845.827882] RAX: ffffffffffffffda RBX: 00007f8b863fccdc RCX: 00007f8d9c124ded [ 845.840591] RDX: 0000000000000000 RSI: 000000000000ae80 RDI: 000000000000001e [ 845.853201] RBP: 00007f8b863f7d20 R08: 0000000000000000 R09: 0000000000000000 [ 845.865776] R10: 0000000000000000 R11: 0000000000000246 R12: 00007f8b863fc6c0 [ 845.878246] R13: ffffffffffffdbf0 R14: 0000000000000007 R15: 00007ffedb593c00 [ 845.890732] </TASK> [ 845.897565] irq event stamp: 859157 [ 845.905815] hardirqs last enabled at (859171): [<ffffffff902447d3>] __up_console_sem+0x63/0x90 [ 845.923321] hardirqs last disabled at (859184): [<ffffffff902447b8>] __up_console_sem+0x48/0x90 [ 845.940892] softirqs last enabled at (859126): [<ffffffff90194ef8>] handle_softirqs+0x358/0x4b0 [ 845.958654] softirqs last disabled at (859207): [<ffffffff901951cf>] __irq_exit_rcu+0xef/0x170 [ 845.976232] ---[ end trace 0000000000000000 ]---
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 17 lines · Yan Zhao
Quoted 4 lines · Ackerley Tng
(quoted diff) +4
-0+static enum shareability kvm_gmem_shareability_get(struct inode *inode, + pgoff_t index) +{ + struct maple_tree *mt;Review
Quoted 10 lines
(quoted diff) +10
-0+ void *entry; + + mt = &kvm_gmem_private(inode)->shareability; + entry = mtree_load(mt, index); + WARN(!entry, + "Shareability should always be defined for all indices in inode."); + + return xa_to_value(entry); +} +Hi Ackerley,
Quoted 2 lines
Not sure if it's a known issue. Just want to let you know in case you're unaware.
Thanks for informing me, and thanks for the analysis :)
Quoted 19 lines
During a test to repeatedly launching/destroying TDs, I encountered a warning from
kvm_gmem_shareability_get()(see the attached log at the bottom). The reproducing rate is 1 in every 20-100 times of launching TD.After some analysis, I found that the warning was produced by
kvm_gmem_shareability_get()when it's called fromkvm_gmem_is_private(), which is not protected by any locks.I can get rid of the warning by either fix 1 or fix 2 below. (I prefer fix 1 though :))
fix 1:
virt/kvm/guest_memfd.c +0
-0
virt/kvm/guest_memfd.c static struct inode *kvm_gmem_inode_make_secure_inode(const char *name, · line 2030
Quoted 8 lines
virt/kvm/guest_memfd.c +1
-1@@ -2024,7 +2024,7 @@ static struct inode *kvm_gmem_inode_make_secure_inode(const char *name,#ifdef CONFIG_KVM_GMEM_SHARED_MEM if (flags &GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { -mt_init (&private->shareability); +mt_init_flags (&private->shareability,MT_FLAGS_USE_RCU ); err =kvm_gmem_shareability_setup (private, size, flags);if (err)
Not sure about the version of the conversion patch series that you're using, in the version I'm preparing, I'm using filemap_invalidate_lock_shared() to guard shareability reads. filemap_invalidate_lock() is held during shareability updates, so I think this issue should be fixed.
Please let me know if you're still seeing this issue in the next series (coming soon). Thank you!
Quoted 23 lines
fix 2:
virt/kvm/guest_memfd.c +2
-0@@ -171,7 +171,9 @@ static enum shareability kvm_gmem_shareability_get(struct inode *inode,void *entry; mt = &kvm_gmem_private (inode)->shareability; +mtree_lock (mt); entry =mtree_load (mt, index); +mtree_unlock (mt);WARN (!entry,"Shareability should always be defined for all indices in inode." );Thanks
Yan[...snip...]
Sean Christopherson
maintainerRe: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngTrimmed the Cc substantially as I doubt non-gmem/KVM folks will be excited about thread necromancy.
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6414
Quoted 12 lines · Ackerley Tng
Documentation/virt/kvm/api.rst +4
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM. +If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host.
Whatever documentation we add should land at the same time as the collateral. KVM_CAP_GMEM_CONVERSIONS literally doesn't exist at this time.
Quoted 4 lines
Documentation/virt/kvm/api.rst +0
-0@@ -17,6 +18,24 @@ struct kvm_gmem { struct list_head entry; };
Documentation/virt/kvm/api.rst struct kvm_gmem { · line 30
Quoted 10 lines
Documentation/virt/kvm/api.rst +10
-0+struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + struct maple_tree shareability; +#endif +}; + +enum shareability { + SHAREABILITY_GUEST = 1, /* Only the guest can map (fault) folios in this range. */ + SHAREABILITY_ALL = 2, /* Both guest and host can fault folios in this range. */ +};
Rather than define new values and new KVM uAPI, I think we should instead simply support KVM_SET_MEMORY_ATTRIBUTES. We'll probably need a new CAP, as I'm not sure supporting KVM_CHECK_EXTENSION+KVM_CAP_MEMORY_ATTRIBUTES on a gmem fd would be a good idea (e.g. trying to do KVM_CAP_GUEST_MEMFD_FLAGS on a gmem fd doesn't work because the whole point is to get flags _before_ creating the gmem instance). But adding e.g. KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES is easy enough.
But for specifying PRIVATE vs. SHARED, I don't see any reason to define new uAPI. I also don't want an entirely new set of terms in KVM to describe the same things. PRIVATE and SHARED are far from perfect, but they're better than https://xkcd.com/927.
And if we ever want to let userspace restrict RWX protections in gmem, we'll have a ready-made way to do so.
Internally, that let's us do some fun things in KVM. E.g. if we make the "disable legacy per-VM memory attributes" a read-only module param, then we can wire up a static_call() for kvm_get_memory_attributes() and then kvm_mem_is_private() will Just Work.
static inline unsigned long kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn)
{
return static_call(__kvm_get_memory_attributes)(kvm, gfn);}
static inline bool kvm_mem_is_private(struct kvm *kvm, gfn_t gfn)
{
return kvm_get_memory_attributes(kvm, gfn) & KVM_MEMORY_ATTRIBUTE_PRIVATE;}
That might trigger some additional surgery if/when we want to support RWX protections on a per-VM basis _and_ a per-gmem basic, but I suspect such churn would pale in comparison to the overall support needed for RWX protections.
The kvm_memory_attributes structure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used for guest_memfd.
include/uapi/linux/kvm.h +4 -1
@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;
Documentation/virt/kvm/api.rst struct kvm_gmem { · line 35
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private(struct inode *inode) +{ + return inode->i_mapping->i_private_data;
This is a hilarious bad helper. Everyone and their mother is going to think about "private vs. shared" when they see kvm_gmem_private(), at least on the x86 side.
What's even more absurd is that the only "final" usage of the helper is to free/destroy the inode:
$ git grep kvm_gmem_private virt/kvm/guest_memfd.c:static struct kvm_gmem_inode_private *kvm_gmem_private(struct inode *inode) virt/kvm/guest_memfd.c: return kvm_gmem_private(inode)->allocator_ops; virt/kvm/guest_memfd.c: return kvm_gmem_private(inode)->allocator_private; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: struct kvm_gmem_inode_private *private = kvm_gmem_private(inode); virt/kvm/guest_memfd.c: struct kvm_gmem_inode_private *private = kvm_gmem_private(inode);
And in that case, using a wrapper is counter-productive, just reference inode->i_mapping->i_private_data directly so that readeres don't have to jump through a useless layer.
Luckily, "struct kvm_gmem_inode_private" no longer needs to exist, now that Shivank's NUMA policy series wraps the vfs_inode with a gmem_inode, and can be retrieved via GMEM_I(). FWIW, before looking that series, I was going to suggest something like to_gmem(), but I definitely think we should follow filesystems convention, not KVM vCPU/VM convention.
Quoted 20 lines
Documentation/virt/kvm/api.rst +14
-0* folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index.@@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) return folio_pfn(folio) + (index & (folio_nr_pages(folio) - 1)); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private, + loff_t size, u64 flags) +{ + enum shareability m; + pgoff_t last; + + last = (size >> PAGE_SHIFT) - 1; + m = flags & GUEST_MEMFD_FLAG_INIT_PRIVATE ? SHAREABILITY_GUEST : + SHAREABILITY_ALL; + return mtree_store_range(&private->shareability, 0, last, xa_mk_value(m), + GFP_KERNEL); +}
Documentation/virt/kvm/api.rst static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 74
Quoted 10 lines
Documentation/virt/kvm/api.rst +10
-0+ +static enum shareability kvm_gmem_shareability_get(struct inode *inode, + pgoff_t index) +{ + struct maple_tree *mt; + void *entry; + + mt = &kvm_gmem_private(inode)->shareability; + entry = mtree_load(mt, index); + WARN(!entry,
WARN_ON_ONCE(), otherwise we risk escalating a per-VM problem into a system-wide DoS.
Documentation/virt/kvm/api.rst static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 85
Quoted 11 lines
Documentation/virt/kvm/api.rst +11
-0+ "Shareability should always be defined for all indices in inode."); + + return xa_to_value(entry); +} + +static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t index) +{ + if (kvm_gmem_shareability_get(inode, index) != SHAREABILITY_ALL) + return ERR_PTR(-EACCES); + + return kvm_gmem_get_folio(inode, index);
Please don't add 1-3 line helpers with one caller and very little hope of gaining additional users, especially in guest_memfd where "shared" and "private" have multiple meanings, and so things like "get_shared_folio" are inherently ambiguous.
I'm pretty sure a lot of this stems from CONFIG_KVM_GMEM_SHARED_MEM, which AFAICT simply won't exist. But just in case this is a Google3 pattern...
Quoted 21 lines
Documentation/virt/kvm/api.rst +17
-0+} + +#else + +static int kvm_gmem_shareability_setup(struct maple_tree *mt, loff_t size, u64 flags) +{ + return 0; +} + +static inline struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") + return NULL; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ + static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct kvm_memory_slot *slot, pgoff_t index, struct folio *folio) {
Documentation/virt/kvm/api.rst static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 5 lines
Documentation/virt/kvm/api.rst +1
-1@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);
I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
The locking rules for the maple_tree are also undocumented and haphazard. I think we can kill several birds with one stone by protecting the attributes with invalidate_lock. A bonus with using invalidate_lock is that it's a sleepable lock, not a spinlock. I don't think there's anything that would immediately benefit? But if we wanted to populate the tree on-demand (versus pre-filling all possible pages), then it'd be easier to handle things like allocations in a race free manner.
/* * Protect the attributes with the invalidation lock, which will always * be held on conversions */ mt_init_flags(&gi->attributes, MT_FLAGS_LOCK_EXTERN); mt_set_external_lock(&gi->attributes, &inode->i_mapping->invalidate_lock);
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 12 lines · Sean Christopherson
Trimmed the Cc substantially as I doubt non-gmem/KVM folks will be excited about thread necromancy.
Quoted 8 lines · Ackerley Tng
Documentation/virt/kvm/api.rst +0
-0@@ -6408,6 +6408,11 @@ belonging to the slot via its userspace_addr. The use of GUEST_MEMFD_FLAG_SUPPORT_SHARED will not be allowed for CoCo VMs. This is validated when the guest_memfd instance is bound to the VM.
Quoted 8 lines
Documentation/virt/kvm/api.rst belonging to the slot via its userspace_addr. · line 6414
Quoted 4 lines
Documentation/virt/kvm/api.rst +4
-0+If the capability KVM_CAP_GMEM_CONVERSIONS is supported, then the 'flags' field +supports GUEST_MEMFD_FLAG_INIT_PRIVATE. Setting GUEST_MEMFD_FLAG_INIT_PRIVATE +will initialize the memory for the guest_memfd as guest-only and not faultable +by the host.Whatever documentation we add should land at the same time as the collateral.
KVM_CAP_GMEM_CONVERSIONSliterally doesn't exist at this time.
Thanks, will keep this in mind for next time.
Quoted 13 lines
Quoted 13 lines
Documentation/virt/kvm/api.rst +9
-0@@ -17,6 +18,24 @@ struct kvm_gmem { struct list_head entry; }; +struct kvm_gmem_inode_private { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + struct maple_tree shareability; +#endif +}; + +enum shareability { + SHAREABILITY_GUEST = 1, /* Only the guest can map (fault) folios in this range. */ + SHAREABILITY_ALL = 2, /* Both guest and host can fault folios in this range. */
Quoted 9 lines
Documentation/virt/kvm/api.rst struct kvm_gmem { · line 30
Quoted 1 line
Documentation/virt/kvm/api.rst +1
-0+};Rather than define new values and new KVM uAPI, I think we should instead simply support
KVM_SET_MEMORY_ATTRIBUTES. We'll probably need a new CAP, as I'm not sure supporting KVM_CHECK_EXTENSION+KVM_CAP_MEMORY_ATTRIBUTES on a gmem fd would be a good idea (e.g. trying to doKVM_CAP_GUEST_MEMFD_FLAGSon a gmem fd doesn't work because the whole point is to get flags _before_ creating the gmem instance). But adding e.g.KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTESis easy enough.
I've read this a few times and I'm a bit confused, so just making sure:
you are suggesting that we reuse the KVM_SET_MEMORY_ATTRIBUTES ioctl as a guest_memfd (not a VM) ioctl and still store private/shared state within guest_memfd, right?
I think fundamentally the introduction of the guest_memfd ioctls was motivated by how private/shared state is a property of memory and not a property of the VM. (IIRC you were the one to most succinctly phrase it this way on one of the guest_memfd biweeklies.) So I hope you don't mean to revert to doing conversions through a VM ioctl.
Quoted 6 lines
But for specifying PRIVATE vs. SHARED, I don't see any reason to define new uAPI. I also don't want an entirely new set of terms in KVM to describe the same things. PRIVATE and SHARED are far from perfect, but they're better than https://xkcd.com/927.
And if we ever want to let userspace restrict RWX protections in gmem, we'll have a ready-made way to do so.
Would like to understand more about RWX protections: is the use case to let userspace specify that certain ranges of guest memory are to be mapped into stage 2 page tables without executable permissions?
Is there a further use case to let the guest specify that userspace must not mmap() some ranges as executable?
For guest_memfd the userspace mapping permissions are already defined by userspace and so unless guest_memfd must enforce something on behalf of the guest, there shouldn't be anything more that guest_memfd should track with respect to RWX permissions.
Quoted 10 lines
Internally, that let's us do some fun things in KVM. E.g. if we make the "disable legacy per-VM memory attributes" a read-only module param, then we can wire up a
static_call()forkvm_get_memory_attributes()and thenkvm_mem_is_private()will Just Work.static inline unsigned long
kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn){ return static_call(__kvm_get_memory_attributes)(kvm, gfn);}
Quoted 9 lines
static inline bool
kvm_mem_is_private(struct kvm *kvm, gfn_t gfn){ return kvm_get_memory_attributes(kvm, gfn) & KVM_MEMORY_ATTRIBUTE_PRIVATE;}
That might trigger some additional surgery if/when we want to support RWX protections on a per-VM basis _and_ a per-gmem basic, but I suspect such churn would pale in comparison to the overall support needed for RWX protections.
RWX protections are more of a VM-level property, if I understood the use case correctly that some gfn ranges are to be marked non-executable by userspace. Setting RWX within guest_memfd would be kind of awkward since userspace must first translate GFN to offset, then set it using the offset within guest_memfd. Hence I think it's okay to have RWX stuff go through the regular KVM_SET_MEMORY_ATTRIBUTES *VM* ioctl and have it tracked in mem_attr_array.
I'd prefer not to have the module param choose between the use of mem_attr_array and guest_memfd conversion in case we need both mem_attr_array to support other stuff in future while supporting conversions.
Quoted 10 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)
include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;
struct kvm_memory_attributes doesn't have room for reporting the offset at which conversion failed (error_offset in the new struct). How do we handle this? Do we reuse the flags field, or do we not report error_offset?
Quoted 10 lines
Documentation/virt/kvm/api.rst struct kvm_gmem { · line 35
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private(struct inode *inode) +{ + return inode->i_mapping->i_private_data;This is a hilarious bad helper. Everyone and their mother is going to think about "private vs. shared" when they see
kvm_gmem_private(), at least on the x86 side.
Totally missed this interpretation of private, lol. Too many interpretations of private: MAP_PRIVATE, CoCo's private vs shared, and i_private_data.
Quoted 18 lines
What's even more absurd is that the only "final" usage of the helper is to free/destroy the inode:
$ git grep kvm_gmem_private virt/kvm/guest_memfd.c:static struct kvm_gmem_inode_private *kvm_gmem_private(struct inode *inode) virt/kvm/guest_memfd.c: return kvm_gmem_private(inode)->allocator_ops; virt/kvm/guest_memfd.c: return kvm_gmem_private(inode)->allocator_private; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: mt = &kvm_gmem_private(inode)->shareability; virt/kvm/guest_memfd.c: struct kvm_gmem_inode_private *private = kvm_gmem_private(inode); virt/kvm/guest_memfd.c: struct kvm_gmem_inode_private *private = kvm_gmem_private(inode);And in that case, using a wrapper is counter-productive, just reference
inode->i_mapping->i_private_datadirectly so that readeres don't have to jump through a useless layer.
Quoted 6 lines
Luckily, "struct
kvm_gmem_inode_private" no longer needs to exist, now that Shivank's NUMA policy series wraps thevfs_inodewith agmem_inode, and can be retrieved viaGMEM_I(). FWIW, before looking that series, I was going to suggest something liketo_gmem(), but I definitely think we should follow filesystems convention, not KVM vCPU/VM convention.
I'll align with the wrapper struct to align with filesystems conventions then.
Quoted 25 lines
Quoted 25 lines
Documentation/virt/kvm/api.rst +19
-0* folio_file_pfn - like folio_file_page, but return a pfn. * @folio: The folio which contains this index.@@ -29,6 +48,58 @@ static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) return folio_pfn(folio) + (index & (folio_nr_pages(folio) - 1)); } +#ifdef CONFIG_KVM_GMEM_SHARED_MEM + +static int kvm_gmem_shareability_setup(struct kvm_gmem_inode_private *private, + loff_t size, u64 flags) +{ + enum shareability m; + pgoff_t last; + + last = (size >> PAGE_SHIFT) - 1; + m = flags & GUEST_MEMFD_FLAG_INIT_PRIVATE ? SHAREABILITY_GUEST : + SHAREABILITY_ALL; + return mtree_store_range(&private->shareability, 0, last, xa_mk_value(m), + GFP_KERNEL); +} + +static enum shareability kvm_gmem_shareability_get(struct inode *inode, + pgoff_t index) +{ + struct maple_tree *mt;
Quoted 9 lines
Documentation/virt/kvm/api.rst static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 74
Quoted 5 lines
Documentation/virt/kvm/api.rst +5
-0+ void *entry; + + mt = &kvm_gmem_private(inode)->shareability; + entry = mtree_load(mt, index); + WARN(!entry,
WARN_ON_ONCE(), otherwise we risk escalating a per-VM problem into a system-wide DoS.
Will take note for next time.
Quoted 16 lines
Documentation/virt/kvm/api.rst static inline kvm_pfn_t folio_file_pfn(struct folio *folio, pgoff_t index) · line 85
Quoted 11 lines
Documentation/virt/kvm/api.rst +11
-0+ "Shareability should always be defined for all indices in inode."); + + return xa_to_value(entry); +} + +static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t index) +{ + if (kvm_gmem_shareability_get(inode, index) != SHAREABILITY_ALL) + return ERR_PTR(-EACCES); + + return kvm_gmem_get_folio(inode, index);Please don't add 1-3 line helpers with one caller and very little hope of gaining additional users, especially in
guest_memfdwhere "shared" and "private" have multiple meanings, and so things like "get_shared_folio" are inherently ambiguous.
Quoted 3 lines
I'm pretty sure a lot of this stems from
CONFIG_KVM_GMEM_SHARED_MEM, which AFAICT simply won't exist. But just in case this is a Google3 pattern...
Will take note for next time.
Quoted 22 lines
Quoted 22 lines
Documentation/virt/kvm/api.rst +17
-0+} + +#else + +static int kvm_gmem_shareability_setup(struct maple_tree *mt, loff_t size, u64 flags) +{ + return 0; +} + +static inline struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") + return NULL; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ + static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct kvm_memory_slot *slot, pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)
Quoted 9 lines
Documentation/virt/kvm/api.rst static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
Documentation/virt/kvm/api.rst +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Conversions take the filemap_invalidate_lock() too, along with allocations, truncations.
Because the filemap_invalidate_lock() might be reused for other fs-specific operations, I didn't do the mt_set_external_lock() thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.
Quoted 15 lines
The locking rules for the
maple_treeare also undocumented and haphazard. I think we can kill several birds with one stone by protecting the attributes withinvalidate_lock. A bonus with usinginvalidate_lockis that it's a sleepable lock, not a spinlock. I don't think there's anything that would immediately benefit? But if we wanted to populate the tree on-demand (versus pre-filling all possible pages), then it'd be easier to handle things like allocations in a race free manner./* * Protect the attributes with the invalidation lock, which will always * be held on conversions */ mt_init_flags(&gi->attributes, MT_FLAGS_LOCK_EXTERN); mt_set_external_lock(&gi->attributes, &inode->i_mapping->invalidate_lock);
Sean Christopherson
maintainerRe: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 15 lines · Ackerley Tng
Quoted 13 lines · Sean Christopherson
Review
Quoted 4 lines · Ackerley Tng
(quoted diff) +4
-0+enum shareability { + SHAREABILITY_GUEST = 1, /* Only the guest can map (fault) folios in this range. */ + SHAREABILITY_ALL = 2, /* Both guest and host can fault folios in this range. */ +};Rather than define new values and new KVM uAPI, I think we should instead simply support
KVM_SET_MEMORY_ATTRIBUTES. We'll probably need a new CAP, as I'm not sure supporting KVM_CHECK_EXTENSION+KVM_CAP_MEMORY_ATTRIBUTES on a gmem fd would be a good idea (e.g. trying to doKVM_CAP_GUEST_MEMFD_FLAGSon a gmem fd doesn't work because the whole point is to get flags _before_ creating the gmem instance). But adding e.g.KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTESis easy enough.
Quoted 4 lines
I've read this a few times and I'm a bit confused, so just making sure:
you are suggesting that we reuse theKVM_SET_MEMORY_ATTRIBUTESioctl as aguest_memfd(not a VM) ioctl and still store private/shared state withinguest_memfd, right?
Yep. Something like:
static long kvm_gmem_set_attributes(struct file *file, void __user *argp)
{
struct gmem_file *f = file->private_data;
struct inode *inode = file_inode(file);
struct kvm_memory_attributes attrs;
pgoff_t err_index;
int r;
if (copy_from_user(&attrs, argp, sizeof(attrs)))
return -EFAULT;
if (attrs.flags)
return -EINVAL;
if (attrs.attributes & ~kvm_gmem_supported_mem_attributes(f))
return -EINVAL;
if (attrs.size == 0 || attrs.offset + attrs.size < attrs.offset)
return -EINVAL;
if (!PAGE_ALIGNED(attrs.offset) || !PAGE_ALIGNED(attrs.offset))
return -EINVAL;
if (attrs.offset > inode->i_size ||
attrs.offset + attrs.size > inode->i_size)
return -EINVAL;
r = __kvm_gmem_set_attributes(inode, &attrs, &err_index);
if (r) {
attrs.offset = err_index << PAGE_SHIFT;
if (copy_to_user(argp, &attrs, sizeof(attrs)))
return -EFAULT;
return r;
}
return 0;
}static long kvm_gmem_ioctl(struct file *file, unsigned int ioctl,
unsigned long arg)
{
switch (ioctl) {
case KVM_SET_MEMORY_ATTRIBUTES:
return kvm_gmem_set_attributes(file, (void __user *)arg);
default:
return -ENOTTY;
}
}Quoted 5 lines
I think fundamentally the introduction of the
guest_memfdioctls was motivated by how private/shared state is a property of memory and not a property of the VM. (IIRC you were the one to most succinctly phrase it this way on one of theguest_memfdbiweeklies.) So I hope you don't mean to revert to doing conversions through a VM ioctl.
I do not. Ah shoot. I responded to my (much earlier) mail on this to clarify exactly this point, but I botched the Cc and threading, and it didn't make it's way to you.
https://lore.kernel.org/all/[email protected]
Quoted 10 lines
Quoted 6 lines
But for specifying PRIVATE vs. SHARED, I don't see any reason to define new uAPI. I also don't want an entirely new set of terms in KVM to describe the same things. PRIVATE and SHARED are far from perfect, but they're better than https://xkcd.com/927.
And if we ever want to let userspace restrict RWX protections in gmem, we'll have a ready-made way to do so.Would like to understand more about RWX protections: is the use case to let userspace specify that certain ranges of guest memory are to be mapped into stage 2 page tables without executable permissions?
Yep. Or execute-only. Or read-only. The primary use case I'm aware of is for supporting things VBS (Hyper-V's virtualization based security) and HEKI[1] (which is effectively the same thing as VBS, and is indeed being dropped in favor of simply piggybacking the VBS guest<=>host ABI).
VBS allows the kernel to deprivilege itself, and hoist a small amount of code into a more privilege "thing". In KVM, the separate privilege domains will be called "planes"[2]. For RWX protections, the more privileged plane would have full RWX access to all of guest memory, while the deprivilege kernel will have select chunks of memory mapped RO (e.g. kernel page tables, GDT, IDT, etc., or potentially not at all (see Credential Guard).
I don't know if tracking per-plane RWX state in guest_memfd would be a good idea, but it costs practically nothing to keep the possibility open.
[1] https://lore.kernel.org/all/[email protected]
[2] https://lore.kernel.org/all/[email protected]
Quoted 7 lines
Is there a further use case to let the guest specify that userspace must not
mmap()some ranges as executable?For
guest_memfdthe userspace mapping permissions are already defined by userspace and so unlessguest_memfdmust enforce something on behalf of the guest, there shouldn't be anything more thatguest_memfdshould track with respect to RWX permissions.
But not all userspaces are created equal. E.g. if a VM is sharing memory with another entity, it might want to restrict that sharing to be read-only. I don't know that memory attributes would be the best way to express such rules, just saying that fully relying on mmap() has limitations.
Quoted 20 lines
Quoted 10 lines
Internally, that let's us do some fun things in KVM. E.g. if we make the "disable legacy per-VM memory attributes" a read-only module param, then we can wire up a
static_call()forkvm_get_memory_attributes()and thenkvm_mem_is_private()will Just Work.static inline unsigned long
kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn){ return static_call(__kvm_get_memory_attributes)(kvm, gfn);}
Quoted 9 lines
static inline bool
kvm_mem_is_private(struct kvm *kvm, gfn_t gfn){ return kvm_get_memory_attributes(kvm, gfn) & KVM_MEMORY_ATTRIBUTE_PRIVATE;}
That might trigger some additional surgery if/when we want to support RWX protections on a per-VM basis _and_ a per-gmem basic, but I suspect such churn would pale in comparison to the overall support needed for RWX protections.
Quoted 7 lines
RWX protections are more of a VM-level property, if I understood the use case correctly that some gfn ranges are to be marked non-executable by userspace. Setting RWX within
guest_memfdwould be kind of awkward since userspace must first translate GFN to offset, then set it using the offset withinguest_memfd. Hence I think it's okay to have RWX stuff go through the regularKVM_SET_MEMORY_ATTRIBUTES*VM* ioctl and have it tracked inmem_attr_array.
Maybe. It will depend on how the use cases shake out. E.g. before the planes idea came along, the proposal for supporting different privilege levels was to represent each privilege level with its own "struct kvm", at which point tracking RWX protections per-VM (struct kvm) made sense.
But with planes, that's no longer true. E.g. we'd need RWX flags for plane0 and separate RWX flags for plane1 regardless of whether they're tracked in struct kvm or in the gmem instance.
To be clear, I don't have an opinion one way or the other, because what we'll end up with is quite unclear. All I was calling out is that reusing KVM_SET_MEMORY_ATTRIBUTES provides a lot of the plumbing, _if_ we want to define RWX protections on a gmem instance.
Quoted 4 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.
Luckily, we don't actually need to make a decision on this, because PRIVATE is the only attribute that exists. Which is partly why I want to go with a module param. We can make the behavior very definitive without significant risk of causing ABI hell.
It's entirely possible I'm completely wrong and we'll end up with per-VM RWX protections and no other per-gmem memory attributes, but as above, unwinding or adjusting the module param will be a drop in the bucket compared to the effort needed to add whatever support comes along.
Quoted 10 lines
Quoted 10 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)
Quoted 15 lines
include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;struct
kvm_memory_attributesdoesn't have room for reporting the offset at which conversion failed (error_offsetin the new struct). How do we handle this? Do we reuse the flags field, or do we not reporterror_offset?
Write back at address/offset (and update size too, which I probably forgot to do). Ugh, but it's defined _IOW. I forget if that matters in practice (IIRC, it's not enforced anywhere, i.e. purely informational for userspace).
Quoted 15 lines
Quoted 5 lines
Quoted 5 lines
static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct
kvm_memory_slot*slot,(quoted diff) +0
-0pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)Quoted 9 lines
Review static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
(quoted diff) +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Quoted 7 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.
mt_set_external_lock() is a nop. It exists purely for lockdep assertions. Per the comment for MT_FLAGS_LOCK_EXTERN, "mt_lock is not used", LOCK_EXTERN simply
tells maple tree to not use/take mt_lock. I.e. it doesn't say "take this lock
instead", it says "I'll handle locking".
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 76 lines · Sean Christopherson
Quoted 15 lines · Ackerley Tng
Quoted 13 lines · Sean Christopherson
Review
Quoted 4 lines · Ackerley Tng
(quoted diff) +4
-0+enum shareability { + SHAREABILITY_GUEST = 1, /* Only the guest can map (fault) folios in this range. */ + SHAREABILITY_ALL = 2, /* Both guest and host can fault folios in this range. */ +};Rather than define new values and new KVM uAPI, I think we should instead simply support
KVM_SET_MEMORY_ATTRIBUTES. We'll probably need a new CAP, as I'm not sure supporting KVM_CHECK_EXTENSION+KVM_CAP_MEMORY_ATTRIBUTES on a gmem fd would be a good idea (e.g. trying to doKVM_CAP_GUEST_MEMFD_FLAGSon a gmem fd doesn't work because the whole point is to get flags _before_ creating the gmem instance). But adding e.g.KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTESis easy enough.Quoted 4 lines
I've read this a few times and I'm a bit confused, so just making sure:
you are suggesting that we reuse theKVM_SET_MEMORY_ATTRIBUTESioctl as aguest_memfd(not a VM) ioctl and still store private/shared state withinguest_memfd, right?Yep. Something like:
static long
kvm_gmem_set_attributes(struct file *file, void __user *argp){ struct gmem_file *f = file->private_data; struct inode *inode = file_inode(file); struct kvm_memory_attributes attrs; pgoff_t err_index; int r; if (copy_from_user(&attrs, argp, sizeof(attrs))) return -EFAULT; if (attrs.flags) return -EINVAL; if (attrs.attributes & ~kvm_gmem_supported_mem_attributes(f)) return -EINVAL; if (attrs.size == 0 || attrs.offset + attrs.size < attrs.offset) return -EINVAL; if (!PAGE_ALIGNED(attrs.offset) || !PAGE_ALIGNED(attrs.offset)) return -EINVAL; if (attrs.offset > inode->i_size || attrs.offset + attrs.size > inode->i_size) return -EINVAL; r = __kvm_gmem_set_attributes(inode, &attrs, &err_index); if (r) { attrs.offset = err_index << PAGE_SHIFT; if (copy_to_user(argp, &attrs, sizeof(attrs))) return -EFAULT; return r; } return 0; }static long kvm_gmem_ioctl(struct file *file, unsigned int ioctl,
unsigned long arg){ switch (ioctl) { case KVM_SET_MEMORY_ATTRIBUTES: return kvm_gmem_set_attributes(file, (void __user *)arg); default: return -ENOTTY; } }Quoted 5 lines
I think fundamentally the introduction of the
guest_memfdioctls was motivated by how private/shared state is a property of memory and not a property of the VM. (IIRC you were the one to most succinctly phrase it this way on one of theguest_memfdbiweeklies.) So I hope you don't mean to revert to doing conversions through a VM ioctl.
Quoted 6 lines
I do not. Ah shoot. I responded to my (much earlier) mail on this to clarify exactly this point, but I botched the Cc and threading, and it didn't make it's way to you.
Phew. Good to have this clarified.
Quoted 8 lines
Quoted 7 lines
[...snip...]
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.
Quoted 5 lines
Luckily, we don't actually need to make a decision on this, because PRIVATE is the only attribute that exists. Which is partly why I want to go with a module param. We can make the behavior very definitive without significant risk of causing ABI hell.
Then maybe I'm misunderstanding the static_call() thing you were describing. Is it like, at KVM module initialization time,
if module_param == disable_tracking:
.__kvm_get_memory_attributes = read_attributes_from_guest_memfd
else
.__kvm_get_memory_attributes = read_attributes_from_mem_attr_array
With that, I can't have both CoCo private/shared state tracked in guest_memfd and RWX (as an example, could be any future attribute) tracked in mem_attr_array on the same VM.
Quoted 5 lines
It's entirely possible I'm completely wrong and we'll end up with per-VM RWX protections and no other per-gmem memory attributes, but as above, unwinding or adjusting the module param will be a drop in the bucket compared to the effort needed to add whatever support comes along.
Is a module param a weaker userspace contract such that the definition for module params can be more flexibly adjusted?
Quoted 26 lines
Quoted 10 lines
Quoted 10 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)Quoted 15 lines
include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;struct
kvm_memory_attributesdoesn't have room for reporting the offset at which conversion failed (error_offsetin the new struct). How do we handle this? Do we reuse the flags field, or do we not reporterror_offset?
Quoted 1 line
Write back at address/offset
I think it might be surprising to the userspace program, when it wants to check the offset that it had requested and found that it changed due to an error, or upon decoding the error, be unable to find the original offset it had requested. Like,
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx", attr.offset, attr.size, attr.error_offset)
would be nicer than
original_offset = attr.offset
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx", original_offset, attr.size, attr.error_offset)
Quoted 1 line
(and update size too, which I probably forgot to do).
Why does size need to be updated? I think u64 for size is great, and size is better than nr_pages since nr_pages differs on different platforms based on PAGE_SIZE and also nr_pages introduces the question of "was it hugetlb, or a native page size?".
Quoted 3 lines
Ugh, but it's defined
_IOW. I forget if that matters in practice (IIRC, it's not enforced anywhere, i.e. purely informational for userspace).
I didn't notice this IOW vs IORW part, but if it starts getting enforced/specified [1] or auto-documented we'd be in trouble.
At this point, maybe it's better to just have a different ioctl number and struct definition. I feel that it would be easier for a user to associate/separate
+ KVM_SET_MEMORY_ATTRIBUTES
+ Is VM ioctl
+ Is a write-only ioctl
+ Is for setting memory attributes at a VM level
+ Use struct kvm_memory_attributes for this
+ KVM_GUEST_MEMFD_SET_MEMORY_ATTRIBUTES (name TBD)
+ Is guest_memfd ioctl
+ Is a read/write ioctl
+ Is for setting memory attributes only for this guest_memfd
+ Use struct guest_memfd_memory_attributes for this
+ Also decode errors from this struct
[1] https://lore.kernel.org/all/[email protected]/
Quoted 23 lines
Quoted 15 lines
Quoted 5 lines
Quoted 5 lines
static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct
kvm_memory_slot*slot,(quoted diff) +0
-0pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)Quoted 9 lines
Review static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
(quoted diff) +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Quoted 7 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.
Quoted 4 lines
mt_set_external_lock()is a nop. It exists purely for lockdep assertions. Per the comment forMT_FLAGS_LOCK_EXTERN, "mt_lockis not used",LOCK_EXTERNsimply
tells maple tree to not use/takemt_lock. I.e. it doesn't say "take this lock
instead", it says "I'll handle locking".
Thanks for pointing this out!
Conversions (and others) taking the filemap_invalidate_lock() probably fixes the TOCTOU bug, right?
Sean Christopherson
maintainerRe: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 15 lines · Ackerley Tng
Quoted 10 lines · Sean Christopherson
Quoted 4 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.Luckily, we don't actually need to make a decision on this, because PRIVATE is the only attribute that exists. Which is partly why I want to go with a module param. We can make the behavior very definitive without significant risk of causing ABI hell.
Then maybe I'm misunderstanding the
static_call()thing you were describing. Is it like, at KVM module initialization time,
Quoted 8 lines
if
module_param==disable_tracking:
.__kvm_get_memory_attributes =read_attributes_from_guest_memfd
else
.__kvm_get_memory_attributes =read_attributes_from_mem_attr_arrayWith that, I can't have both CoCo private/shared state tracked in
guest_memfdand RWX (as an example, could be any future attribute) tracked inmem_attr_arrayon the same VM.
More or less.
Quoted 8 lines
Quoted 5 lines
It's entirely possible I'm completely wrong and we'll end up with per-VM RWX protections and no other per-gmem memory attributes, but as above, unwinding or adjusting the module param will be a drop in the bucket compared to the effort needed to add whatever support comes along.
Is a module param a weaker userspace contract such that the definition for module params can be more flexibly adjusted?
Yes, much weaker.
Quoted 26 lines
Quoted 26 lines
Quoted 25 lines
Quoted 10 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;struct
kvm_memory_attributesdoesn't have room for reporting the offset at which conversion failed (error_offsetin the new struct). How do we handle this? Do we reuse the flags field, or do we not reporterror_offset?
Quoted 6 lines
Quoted 1 line
Write back at address/offset
I think it might be surprising to the userspace program, when it wants to check the offset that it had requested and found that it changed due to an error, or upon decoding the error, be unable to find the original offset it had requested.
It's a somewhat common pattern in the kernel. Updating the offset+size is most often used with -EAGAIN to say "got this far, try the syscall again from this point".
Quoted 11 lines
Like,
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx", attr.offset, attr.size, attr.error_offset)would be nicer than
original_offset= attr.offset
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx",original_offset, attr.size, attr.error_offset)
Quoted 6 lines
Quoted 1 line
(and update size too, which I probably forgot to do).
Why does size need to be updated? I think u64 for size is great, and size is better than
nr_pagessincenr_pagesdiffers on different platforms based onPAGE_SIZEand alsonr_pagesintroduces the question of "was it hugetlb, or a native page size?".
I meant update the number of bytes remaining when updating the offset so that userspace can redo the ioctl without having to update parameters.
Quoted 6 lines
Quoted 3 lines
Ugh, but it's defined
_IOW. I forget if that matters in practice (IIRC, it's not enforced anywhere, i.e. purely informational for userspace).I didn't notice this IOW vs IORW part, but if it starts getting enforced/specified [1] or auto-documented we'd be in trouble.
IOW vs IORW is alread specified in the ioctl. More below.
Quoted 3 lines
At this point, maybe it's better to just have a different ioctl number and struct definition. I feel that it would be easier for a user to associate/separate
Amusingly, we'd only need a different name along with the IORW thing. A full ioctl number is comproised of the "directory" (KVM), the number, the size of the payload, and how the payload is accessed.
#define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT))
So this:
#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes) #define KVM_SET_MEMORY_ATTRIBUTES2 _IOWR(KVMIO, 0xd2, struct kvm_memory_attributes2)
actually generates two different values, and so is two different ioctls from a code perspective.
The "size" of the payload is nice to have as it allows userspace to assert that it's passing the right structure, e.g. this static assert from KVM selftests:
#define kvm_do_ioctl(fd, cmd, arg) \
({ \
kvm_static_assert(!_IOC_SIZE(cmd) || sizeof(*arg) == _IOC_SIZE(cmd)); \
ioctl(fd, cmd, arg); \
})Quoted 11 lines
+
KVM_SET_MEMORY_ATTRIBUTES
+ Is VM ioctl
+ Is a write-only ioctl
+ Is for setting memory attributes at a VM level
+ Use structkvm_memory_attributesfor this
+KVM_GUEST_MEMFD_SET_MEMORY_ATTRIBUTES(name TBD)
+ Isguest_memfdioctl
+ Is a read/write ioctl
+ Is for setting memory attributes only for thisguest_memfd
+ Use structguest_memfd_memory_attributesfor this
+ Also decode errors from this struct
+ Has extra padding for future expansion (because why not)
If we really truly need a new ioctl, I'd probably prefer KVM_SET_MEMORY_ATTRIBUTES2. Yeah, it's silly, but I don't think baking GUEST_MEMFD into the names buys us anything. Then we can use KVM_SET_MEMORY_ATTRIBUTES2 on a VM if the need ever arises.
Alternative #1 is to try and unwind on failure, but that gets complex, and it simply can't be done for some CoCo VMs. E.g. a private=>shared conversion for TDX is descrutive.
Alternative #2 is to make the updates atomic and all-or-nothing, which is what we did for per-VM attributes. That's doable, but it'd either be much more complex than telling userspace to retry, or we'd have to lose the maple tree optimizations (which is effectively what we did for per-VM attributes).
Quoted 30 lines
[1] https://lore.kernel.org/all/[email protected]/
Quoted 23 lines
Quoted 15 lines
Quoted 5 lines
Quoted 5 lines
static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct
kvm_memory_slot*slot,(quoted diff) +0
-0pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)Quoted 9 lines
Review static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
(quoted diff) +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Quoted 7 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.Quoted 4 lines
mt_set_external_lock()is a nop. It exists purely for lockdep assertions. Per the comment forMT_FLAGS_LOCK_EXTERN, "mt_lockis not used",LOCK_EXTERNsimply
tells maple tree to not use/takemt_lock. I.e. it doesn't say "take this lock
instead", it says "I'll handle locking".
Quoted 4 lines
Thanks for pointing this out!
Conversions (and others) taking the
filemap_invalidate_lock()probably fixes the TOCTOU bug, right?
Yes, grabbing a reference to the folio under lock and thus elevating its refcount should prevent conversions to private from that point forward, until the PTE is zapped and the folio is released:
filemap_invalidate_lock_shared(inode->i_mapping); if (kvm_gmem_is_shared_mem(inode, vmf->pgoff)) folio = kvm_gmem_get_folio(inode, vmf->pgoff); else folio = ERR_PTR(-EACCES); filemap_invalidate_unlock_shared(inode->i_mapping);
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 21 lines · Sean Christopherson
Quoted 20 lines · Ackerley Tng
Quoted 10 lines · Sean Christopherson
Quoted 4 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.Luckily, we don't actually need to make a decision on this, because PRIVATE is the only attribute that exists. Which is partly why I want to go with a module param. We can make the behavior very definitive without significant risk of causing ABI hell.
Then maybe I'm misunderstanding the
static_call()thing you were describing. Is it like, at KVM module initialization time,if
module_param==disable_tracking:
.__kvm_get_memory_attributes =read_attributes_from_guest_memfd
else
.__kvm_get_memory_attributes =read_attributes_from_mem_attr_array
Quoted 6 lines
Quoted 3 lines
With that, I can't have both CoCo private/shared state tracked in
guest_memfdand RWX (as an example, could be any future attribute) tracked inmem_attr_arrayon the same VM.More or less.
Hm okay. So introducing the module param will only allow the use of one of the following?
+ KVM_SET_MEMORY_ATTRIBUTES (vm ioctl)
+ KVM_SET_MEMORY_ATTRIBUTES2 (guest_memfd ioctl)
Then I guess using a module param which is a weaker userspace contract allows us to later enable both vm and guest_memfd ioctl if the need arises?
Quoted 11 lines
Quoted 8 lines
Quoted 5 lines
It's entirely possible I'm completely wrong and we'll end up with per-VM RWX protections and no other per-gmem memory attributes, but as above, unwinding or adjusting the module param will be a drop in the bucket compared to the effort needed to add whatever support comes along.
Is a module param a weaker userspace contract such that the definition for module params can be more flexibly adjusted?
Yes, much weaker.
I have a new tool in my toolbox now :)
Quoted 28 lines
Quoted 28 lines
Quoted 27 lines
Quoted 10 lines
Quoted 10 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)Quoted 15 lines
include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;struct
kvm_memory_attributesdoesn't have room for reporting the offset at which conversion failed (error_offsetin the new struct). How do we handle this? Do we reuse the flags field, or do we not reporterror_offset?Write back at address/offset
Quoted 9 lines
Quoted 4 lines
I think it might be surprising to the userspace program, when it wants to check the offset that it had requested and found that it changed due to an error, or upon decoding the error, be unable to find the original offset it had requested.
It's a somewhat common pattern in the kernel. Updating the offset+size is most often used with
-EAGAINto say "got this far, try the syscall again from this point".
TIL, thanks!
Quoted 73 lines
Quoted 11 lines
Like,
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx", attr.offset, attr.size, attr.error_offset)would be nicer than
original_offset= attr.offset
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx",original_offset, attr.size, attr.error_offset)Quoted 6 lines
Quoted 1 line
(and update size too, which I probably forgot to do).
Why does size need to be updated? I think u64 for size is great, and size is better than
nr_pagessincenr_pagesdiffers on different platforms based onPAGE_SIZEand alsonr_pagesintroduces the question of "was it hugetlb, or a native page size?".I meant update the number of bytes remaining when updating the offset so that userspace can redo the ioctl without having to update parameters.
Quoted 6 lines
Quoted 3 lines
Ugh, but it's defined
_IOW. I forget if that matters in practice (IIRC, it's not enforced anywhere, i.e. purely informational for userspace).I didn't notice this IOW vs IORW part, but if it starts getting enforced/specified [1] or auto-documented we'd be in trouble.
IOW vs IORW is alread specified in the ioctl. More below.
Quoted 3 lines
At this point, maybe it's better to just have a different ioctl number and struct definition. I feel that it would be easier for a user to associate/separate
Amusingly, we'd only need a different name along with the IORW thing. A full ioctl number is comproised of the "directory" (KVM), the number, the size of the payload, and how the payload is accessed.
#define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT))So this:
#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes) #define KVM_SET_MEMORY_ATTRIBUTES2 _IOWR(KVMIO, 0xd2, struct kvm_memory_attributes2)actually generates two different values, and so is two different ioctls from a code perspective.
The "size" of the payload is nice to have as it allows userspace to assert that it's passing the right structure, e.g. this static assert from KVM selftests:
#define
kvm_do_ioctl(fd, cmd, arg)\({ \ kvm_static_assert(!_IOC_SIZE(cmd) || sizeof(*arg) == _IOC_SIZE(cmd)); \ ioctl(fd, cmd, arg); \ })Quoted 11 lines
+
KVM_SET_MEMORY_ATTRIBUTES
+ Is VM ioctl
+ Is a write-only ioctl
+ Is for setting memory attributes at a VM level
+ Use structkvm_memory_attributesfor this
+KVM_GUEST_MEMFD_SET_MEMORY_ATTRIBUTES(name TBD)
+ Isguest_memfdioctl
+ Is a read/write ioctl
+ Is for setting memory attributes only for thisguest_memfd
+ Use structguest_memfd_memory_attributesfor this
+ Also decode errors from this struct
Quoted 7 lines
+ Has extra padding for future expansion (because why not)
If we really truly need a new ioctl, I'd probably prefer
KVM_SET_MEMORY_ATTRIBUTES2. Yeah, it's silly, but I don't think bakingGUEST_MEMFDinto the names buys us anything. Then we can useKVM_SET_MEMORY_ATTRIBUTES2on a VM if the need ever arises.
I'm for having a new ioctl number and new struct, which are you leaning towards?
As for the naming, I think it's confusing to have something similar, and Ira mentioned it being confusing in the other email too. At the same time, I accept that it's useful if the same struct were to be used for a new iteration of the KVM_SET_MEMORY_ATTRIBUTES VM ioctl in future. No strong preference either way on naming.
Trying to understand the difference between unwind on failure vs all-or-nothing:
Quoted 4 lines
Alternative #1 is to try and unwind on failure, but that gets complex, and it simply can't be done for some CoCo VMs. E.g. a private=>shared conversion for TDX is descrutive.
Unwind on failure is:
1. Store current state
2. Convert
3. Restore current state on conversion failure
Quoted 5 lines
Alternative #2 is to make the updates atomic and all-or-nothing, which is what we did for per-VM attributes. That's doable, but it'd either be much more complex than telling userspace to retry, or we'd have to lose the maple tree optimizations (which is effectively what we did for per-VM attributes).
All-or-nothing:
1. Do everything to make sure conversion doesn't fail, bail early if it fails
2. Convert (always successful)
Is that it?
Zapping private pages from the stage 2 page tables for TDX can't be recovered without help from the guest (I think that's what you're talking about too), although technically I think this zapping step could be delayed right till the end.
Maple tree allocations for conversion could fail, and allocations are a bit more complicated since we try to compact ranges with the same shared/private status into one same maple tree node. Still technically possible, maybe by updating a copy of the maple tree first, then swapping the current maple tree out atomically.
With HugeTLB, undoing HVO needs pages to be allocated, will need more digging into the details to determine if preallocation could work.
I'd still prefer having the option to return an error so that we don't paint ourselves into a corner.
Quoted 5 lines
Quoted 5 lines
[1] https://lore.kernel.org/all/[email protected]/
[...snip...]
Sean Christopherson
maintainerRe: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 33 lines · Ackerley Tng
Quoted 21 lines · Sean Christopherson
Quoted 20 lines · Ackerley Tng
Quoted 10 lines · Sean Christopherson
Quoted 4 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.Luckily, we don't actually need to make a decision on this, because PRIVATE is the only attribute that exists. Which is partly why I want to go with a module param. We can make the behavior very definitive without significant risk of causing ABI hell.
Then maybe I'm misunderstanding the
static_call()thing you were describing. Is it like, at KVM module initialization time,if
module_param==disable_tracking:
.__kvm_get_memory_attributes =read_attributes_from_guest_memfd
else
.__kvm_get_memory_attributes =read_attributes_from_mem_attr_arrayQuoted 6 lines
Quoted 3 lines
With that, I can't have both CoCo private/shared state tracked in
guest_memfdand RWX (as an example, could be any future attribute) tracked inmem_attr_arrayon the same VM.More or less.
Hm okay. So introducing the module param will only allow the use of one of the following?
Quoted 6 lines
+
KVM_SET_MEMORY_ATTRIBUTES(vm ioctl)
+KVM_SET_MEMORY_ATTRIBUTES2(guest_memfdioctl)Then I guess using a module param which is a weaker userspace contract allows us to later enable both vm and
guest_memfdioctl if the need arises?
In theory. More importantly from my perspective, making the knob global instead of per-VM simplifies the implementation.
Quoted 20 lines
Quoted 12 lines
Quoted 11 lines
+
KVM_SET_MEMORY_ATTRIBUTES
+ Is VM ioctl
+ Is a write-only ioctl
+ Is for setting memory attributes at a VM level
+ Use structkvm_memory_attributesfor this
+KVM_GUEST_MEMFD_SET_MEMORY_ATTRIBUTES(name TBD)
+ Isguest_memfdioctl
+ Is a read/write ioctl
+ Is for setting memory attributes only for thisguest_memfd
+ Use structguest_memfd_memory_attributesfor this
+ Also decode errors from this structQuoted 7 lines
+ Has extra padding for future expansion (because why not)
If we really truly need a new ioctl, I'd probably prefer
KVM_SET_MEMORY_ATTRIBUTES2. Yeah, it's silly, but I don't think bakingGUEST_MEMFDinto the names buys us anything. Then we can useKVM_SET_MEMORY_ATTRIBUTES2on a VM if the need ever arises.
Quoted 5 lines
I'm for having a new ioctl number and new struct, which are you leaning towards?
As for the naming, I think it's confusing to have something similar, and Ira mentioned it being confusing in the other email too.
It might sound confusing, but I don't think the _code_ will be confusing. If it's a module param, it's guaranteed to be global for any VMM. Then the conversion code can be a simple redirect, e.g. I would expect/hope the QEMU change to be something like:
accel/kvm/kvm-all.c +4 -0
@@ -1421,6 +1421,10 @@ static int kvm_set_memory_attributes(hwaddr start, uint64_t size, uint64_t attr)int r;assert ((attr & kvm_supported_memory_attributes) == attr); + +if (kvm_use_gmem_attributes) +return kvm_get_gmem_fd (start, size, attr); + attrs.attributes = attr; attrs.address = start; attrs.size = size;
Quoted 12 lines
At the same time, I accept that it's useful if the same struct were to be used for a new iteration of the
KVM_SET_MEMORY_ATTRIBUTESVM ioctl in future. No strong preference either way on naming.Trying to understand the difference between unwind on failure vs all-or-nothing:
Quoted 4 lines
Alternative #1 is to try and unwind on failure, but that gets complex, and it simply can't be done for some CoCo VMs. E.g. a private=>shared conversion for TDX is descrutive.
Quoted 5 lines
Unwind on failure is:
1. Store current state
2. Convert
3. Restore current state on conversion failure
Not quite, the above is missing key steps that cause problems: invalidation and memory allocation.
1. Zap stage-1 mappings
2. Check if range can be converted
3. Zap stage-2 mappings
4. Store state for target range
For TDX, #3 is the point of no return. That means #4 must not fail, because at the very least invididual page conversions need to be atomic.
Quoted 6 lines
Quoted 5 lines
Alternative #2 is to make the updates atomic and all-or-nothing, which is what we did for per-VM attributes. That's doable, but it'd either be much more complex than telling userspace to retry, or we'd have to lose the maple tree optimizations (which is effectively what we did for per-VM attributes).
Quoted 7 lines
All-or-nothing:
1. Do everything to make sure conversion doesn't fail, bail early if it fails
2. Convert (always successful)Is that it?
No, because #1 is non-trivial. The wrinkle is that, without doing an initial pass through mtree, it's impossible to know how many new entries will be needed (which you already know). It's a solvable problem, e.g. in this RFC, it's kinda sorta handled by allocating temporary structures to track the mtree metadata, but that's the type of complexity I want to avoid.
Quoted 3 lines
Zapping private pages from the stage 2 page tables for TDX can't be recovered without help from the guest (I think that's what you're talking about too),
Ya.
Quoted 1 line
although technically I think this zapping step could be delayed right till the end.
Hmm. If we fully committed to blocking all "gets" via the filemap lock, then
yes, I think it could be delayed until the end? Tempting. But as you note
below, I'd prefer to give ourselves an out from an ABI perspective.
Quoted 1 line
Maple tree allocations for conversion could fail,
Ya, in my local version I handle that by pre-allocating.
mas_for_each(&mas, entry, end - 1) {
MA_STATE(m2, &gi->attributes, 0, 0);
if (attrs->attributes == xa_to_value(entry))
continue;
r = kvm_gmem_mas_preallocate(&m2, attrs->attributes, start, end);
if (r) {
*err_index = m2.index;
goto out;
}
unmap_mapping_pages(mapping, start, nr_pages, false);
if (!kvm_gmem_is_safe_for_conversion(inode, start, nr_pages, err_index)) {
mas_destroy(&m2);
r = -EAGAIN;
goto out;
}
kvm_gmem_invalidate_begin(inode, start, end);
mas_store_prealloc(&m2, xa_mk_value(attrs->attributes));
kvm_gmem_invalidate_end(inode, start, end);
}Quoted 4 lines
and allocations are a bit more complicated since we try to compact ranges with the same shared/private status into one same maple tree node. Still technically possible, maybe by updating a copy of the maple tree first, then swapping the current maple tree out atomically.
Heh, more complexity I'd prefer to avoid.
Quoted 2 lines
With HugeTLB, undoing HVO needs pages to be allocated, will need more digging into the details to determine if preallocation could work.
It has to work, otherwise we're hosed. Though it should be noted that "preallocate" here just means before kvm_gmem_invalidate_begin(). Everything up to that point can fail.
The other option in all of this is to add an API to _block_ stage-2 mappings, in TDX terminology. In TDX, BLOCK marks leaf S-EPT entries !PRESENT, but preserves all the metadata. So if we're too scared to do an after-the-fact invalidation, and we need to support failure after kvm_gmem_invalidate_begin(), we could rework the TDX backend to support e.g. kvm_gmem_invalidate_abort(). On begin(), KVM would BLOCK mappings; on abort(), restore; on end() fully remove.
Quoted 2 lines
I'd still prefer having the option to return an error so that we don't paint ourselves into a corner.
Yep, my thoughts exactly.
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngQuoted 36 lines · Ackerley Tng
[...snip...]
Quoted 33 lines
Quoted 32 lines
Quoted 26 lines
Quoted 10 lines
Quoted 10 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)Quoted 15 lines
include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;struct
kvm_memory_attributesdoesn't have room for reporting the offset at which conversion failed (error_offsetin the new struct). How do we handle this? Do we reuse the flags field, or do we not reporterror_offset?Quoted 1 line
Write back at address/offset
I think it might be surprising to the userspace program, when it wants to check the offset that it had requested and found that it changed due to an error, or upon decoding the error, be unable to find the original offset it had requested.
Quoted 28 lines
Quoted 4 lines
It's a somewhat common pattern in the kernel. Updating the offset+size is most often used with
-EAGAINto say "got this far, try the syscall again from this point".TIL, thanks!
Quoted 21 lines
Quoted 11 lines
Like,
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx", attr.offset, attr.size, attr.error_offset)would be nicer than
original_offset= attr.offset
printf("Error during conversion from offset=%lx with size=%lx, at
error_offset=%lx",original_offset, attr.size, attr.error_offset)Quoted 6 lines
Quoted 1 line
(and update size too, which I probably forgot to do).
Why does size need to be updated? I think u64 for size is great, and size is better than
nr_pagessincenr_pagesdiffers on different platforms based onPAGE_SIZEand alsonr_pagesintroduces the question of "was it hugetlb, or a native page size?".I meant update the number of bytes remaining when updating the offset so that userspace can redo the ioctl without having to update parameters.
Was working through this again, I think the attr.offset returned from the conversion ioctl is not the same as other syscalls where an updated offset+size indicates "got this far, try the syscall again from this point".
For the conversion ioctl, -EAGAIN indicates that a some unexpected refcount was first found at offset error_offset, but does not imply that everything up till error_offset had been converted.
This arises when we start to have hugepage support. To restructure hugepage-by-hugepage, we will iterate hugepage-wise and check for elevated refcounts.
Suppose we're converting 10 1G pages and on the 3rd hugepage, the 5th offset has an elevated refcount.
error_offset should be set to the 5th offset in the 3rd hugepage, but userspace should retry beginning at the offset of the 3rd hugepage with size 8G.
If the offset returned to userspace is the 3rd hugepage, then we lose precision. The refcount at the 3rd hugepage could be fine and expected - it is the page at the 5th offset in the 3rd hugepage that is pinned and userspace should be unpin.
So perhaps the interface needs to be defined as
If the error is -EAGAIN:
+ offset: the offset to retry from
+ size: the remaining size to retry
+ error_offset: the offset where an unexpected refcount was found
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
[...snip...]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Ackerley TngQuoted 2 lines · Ackerley Tng
Sean Christopherson <[email protected]> writes:
[snip]
Quoted 29 lines
Quoted 11 lines
Internally, that let's us do some fun things in KVM. E.g. if we make the "disable legacy per-VM memory attributes" a read-only module param, then we can wire up a
static_call()forkvm_get_memory_attributes()and thenkvm_mem_is_private()will Just Work.static inline unsigned long
kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn){ return static_call(__kvm_get_memory_attributes)(kvm, gfn);}
Quoted 9 lines
static inline bool
kvm_mem_is_private(struct kvm *kvm, gfn_t gfn){ return kvm_get_memory_attributes(kvm, gfn) & KVM_MEMORY_ATTRIBUTE_PRIVATE;}
That might trigger some additional surgery if/when we want to support RWX protections on a per-VM basis _and_ a per-gmem basic, but I suspect such churn would pale in comparison to the overall support needed for RWX protections.
RWX protections are more of a VM-level property, if I understood the use case correctly that some gfn ranges are to be marked non-executable by userspace. Setting RWX within
guest_memfdwould be kind of awkward since userspace must first translate GFN to offset, then set it using the offset withinguest_memfd. Hence I think it's okay to have RWX stuff go through the regularKVM_SET_MEMORY_ATTRIBUTES*VM* ioctl and have it tracked inmem_attr_array.
Quoted 4 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.
I'm getting pretty confused on how userspace is going to know which ioctl to use VM vs gmem.
I was starting to question if going through the VM ioctl should actually change the guest_memfd flags (shareability).
In a prototype I'm playing with shareability has become a bit field which I think aligns with the idea of expanding the memory attributes. But I've had some issues with the TDX tests in trying to decipher when to call vm_set_memory_attributes() vs guest_memfd_convert_private().
Quoted 11 lines
Quoted 11 lines
The
kvm_memory_attributesstructure is compatible, all that's needed AFAICT is a union to clarify it's a pgoff instead of an address when used forguest_memfd.include/uapi/linux/kvm.h +0
-0@@ -1590,7 +1590,10 @@ struct kvm_stats_desc {#define KVM_SET_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd2, struct kvm_memory_attributes)
Quoted 15 lines
include/uapi/linux/kvm.h struct kvm_stats_desc { · line 1599
Quoted 10 lines
include/uapi/linux/kvm.h +4
-1struct kvm_memory_attributes { -__u64 address; +union { +__u64 address; +__u64 offset; + };__u64 size;__u64 attributes;__u64 flags;struct
kvm_memory_attributesdoesn't have room for reporting the offset at which conversion failed (error_offsetin the new struct). How do we handle this? Do we reuse the flags field, or do we not reporterror_offset?
We could extend this for gmem's version of the struct.
Quoted 12 lines
Quoted 11 lines
Review
Quoted 6 lines
(quoted diff) +5
-0+static struct folio *kvm_gmem_get_folio(struct inode *inode, pgoff_t index); + +static struct kvm_gmem_inode_private *kvm_gmem_private(struct inode *inode) +{ + return inode->i_mapping->i_private_data;This is a hilarious bad helper. Everyone and their mother is going to think about "private vs. shared" when they see
kvm_gmem_private(), at least on the x86 side.
Quoted 4 lines
Totally missed this interpretation of private, lol. Too many interpretations of private:
MAP_PRIVATE, CoCo's private vs shared, andi_private_data.
FWIW this did not confuse me and it probably should have... ;-) I'm fine with Sean's suggestion though.
Quoted 32 lines
Quoted 22 lines
Quoted 22 lines
(quoted diff) +17
-0+} + +#else + +static int kvm_gmem_shareability_setup(struct maple_tree *mt, loff_t size, u64 flags) +{ + return 0; +} + +static inline struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t index) +{ + WARN_ONCE("Unexpected call to get shared folio.") + return NULL; +} + +#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ + static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct kvm_memory_slot *slot, pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)Quoted 9 lines
Review static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
(quoted diff) +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Quoted 7 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.
I don't think using the filemap_invalidate_lock() is going to work well here. I've had some hangs on it in my testing and experiments. I think it is better to specifically lock the state tracking itself. I believe Michael mentioned this as well in a previous thread.
Ira
[snip]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 35 lines · Ira Weiny
Quoted 2 lines · Ackerley Tng
Sean Christopherson <[email protected]> writes:
[snip]
Quoted 29 lines
Quoted 11 lines
Internally, that let's us do some fun things in KVM. E.g. if we make the "disable legacy per-VM memory attributes" a read-only module param, then we can wire up a
static_call()forkvm_get_memory_attributes()and thenkvm_mem_is_private()will Just Work.static inline unsigned long
kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn){ return static_call(__kvm_get_memory_attributes)(kvm, gfn);}
Quoted 9 lines
static inline bool
kvm_mem_is_private(struct kvm *kvm, gfn_t gfn){ return kvm_get_memory_attributes(kvm, gfn) & KVM_MEMORY_ATTRIBUTE_PRIVATE;}
That might trigger some additional surgery if/when we want to support RWX protections on a per-VM basis _and_ a per-gmem basic, but I suspect such churn would pale in comparison to the overall support needed for RWX protections.
RWX protections are more of a VM-level property, if I understood the use case correctly that some gfn ranges are to be marked non-executable by userspace. Setting RWX within
guest_memfdwould be kind of awkward since userspace must first translate GFN to offset, then set it using the offset withinguest_memfd. Hence I think it's okay to have RWX stuff go through the regularKVM_SET_MEMORY_ATTRIBUTES*VM* ioctl and have it tracked inmem_attr_array.
Quoted 8 lines
Quoted 4 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.I'm getting pretty confused on how userspace is going to know which ioctl to use VM vs gmem.
It is confusing, yes!
Quoted 3 lines
I was starting to question if going through the VM ioctl should actually change the
guest_memfdflags (shareability).
At one of the guest_memfd biweeklies, we came to the conclusion that we should have a per-VM KVM_CAP_DISABLE_LEGACY_PRIVATE_TRACKING, which will disable the use of just KVM_MEMORY_ATTRIBUTE_PRIVATE for the KVM_SET_MEMORY_ATTRIBUTES ioctl, and KVM_CAP_DISABLE_LEGACY_PRIVATE_TRACKING is the only way to enable conversions for a guest_memfd with mmap() support.
IOW, KVM_CAP_DISABLE_LEGACY_PRIVATE_TRACKING makes userspace choose either
+ Using guest_memfd for private memory and some other memory (e.g. anonymous memory) for shared memory (aka legacy dual backing)
+ And using KVM_SET_MEMORY_ATTRIBUTES VM ioctl for conversions
+ And using mem_attr_array to track shared/private status
+ Using guest_memfd for both private and shared memory (aka single backing)
+ And using the guest_memfd ioctl for conversions
+ And using guest_memfd shareability to track shared/private status
Since userspace has to choose one of the above, there's no point in the VM ioctl affecting shareability.
Sean's suggestion of a module param moves this choice from VM-level to KVM/host-level.
Quoted 2 lines
In a prototype I'm playing with shareability has become a bit field which I think aligns with the idea of expanding the memory attributes.
I guess this is tangentially related and could do with some profiling, but we should be careful about adding too many states in the maple tree.
Conversion iterates over offset ranges in the maple tree, and iteration is faster if there are fewer nodes in the maple tree.
If we just have two states (shared/private) in the maple tree, each node is either all private or all shared.
If we have more states, private ranges might get fragmented based on the orthogonal bits, e.g. RWX, which could then impact conversion performance.
Quoted 4 lines
But I've
had some issues with the TDX tests in trying to decipher when to callvm_set_memory_attributes()vsguest_memfd_convert_private().
Hope the above explanation helps!
+ Legacy dual backing: vm_set_memory_attributes()
+ Single backing: guest_memfd_convert_private()
I don't think this will change much even with the module param, since userspace will still need to know whether to pass in a vm fd or a guest_memfd fd.
Or maybe vm_set_memory_attributes() can take a vm fd, then query module params and then figure out if it should pass vm or guest_mefd fds?
Quoted 26 lines
Quoted 18 lines
[...snip...]
Quoted 5 lines
Quoted 5 lines
static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct
kvm_memory_slot*slot,(quoted diff) +0
-0pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)Quoted 9 lines
Review static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
(quoted diff) +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Quoted 7 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.
Quoted 4 lines
I don't think using the
filemap_invalidate_lock()is going to work well here. I've had some hangs on it in my testing and experiments. I think it is better to specifically lock the state tracking itself. I believe Michael mentioned this as well in a previous thread.
Definitely took the big hammer lock for a start and might be optimizable.
Considerations so far: when a conversion is happening, these have to be locked out:
+ Conversions from competing threads
+ Allocations in kvm_gmem_fault_user_mapping(), because whether an offset can be faulted depends on the outcome of conversion
+ Allocations (fallocate() or kvm_gmem_get_pfn()) and truncations, because conversions (for now) involves removing a folio from the filemap, restructuring, then restoring to the filemap, and
+ Allocations should reuse a folio that was already in the filemap
+ Truncations remove a folio, and should not skip removal of a folio because it was taken out just for conversion
+ memory failure handling, where we don't remove folios from the filemap, but we might restructure, to split huge folios to just unmap pages with failed memory
I think essentially because conversion involves restructuring, and restructuring involves filemap operations and other filemap operations have to wait, conversion also takes the filemap_invalidate_lock() that filemap operations use.
Quoted 4 lines
Ira
[snip]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 8 lines · Ackerley Tng
Quoted 6 lines · Ira Weiny
Quoted 2 lines · Ackerley Tng
Sean Christopherson <[email protected]> writes:
[snip]
[snip]
Quoted 35 lines
Quoted 9 lines
Quoted 5 lines
I'd prefer not to have the module param choose between the use of
mem_attr_arrayandguest_memfdconversion in case we need bothmem_attr_arrayto support other stuff in future while supporting conversions.I'm getting pretty confused on how userspace is going to know which ioctl to use VM vs gmem.
It is confusing, yes!
Quoted 3 lines
I was starting to question if going through the VM ioctl should actually change the
guest_memfdflags (shareability).At one of the
guest_memfdbiweeklies, we came to the conclusion that we should have a per-VMKVM_CAP_DISABLE_LEGACY_PRIVATE_TRACKING, which will disable the use of justKVM_MEMORY_ATTRIBUTE_PRIVATEfor theKVM_SET_MEMORY_ATTRIBUTESioctl, andKVM_CAP_DISABLE_LEGACY_PRIVATE_TRACKINGis the only way to enable conversions for aguest_memfdwithmmap()support.IOW,
KVM_CAP_DISABLE_LEGACY_PRIVATE_TRACKINGmakes userspace choose either+ Using
guest_memfdfor private memory and some other memory (e.g. anonymous memory) for shared memory (aka legacy dual backing)
+ And usingKVM_SET_MEMORY_ATTRIBUTESVM ioctl for conversions
+ And usingmem_attr_arrayto track shared/private status+ Using
guest_memfdfor both private and shared memory (aka single backing)
+ And using theguest_memfdioctl for conversions
+ And usingguest_memfdshareability to track shared/private status
Quoted 5 lines
Since userspace has to choose one of the above, there's no point in the VM ioctl affecting shareability.
Sean's suggestion of a module param moves this choice from VM-level to KVM/host-level.
Ok I remember this discussion but I was not clear on the mechanics. This helps clarify things thanks!
Quoted 10 lines
Quoted 3 lines
In a prototype I'm playing with shareability has become a bit field which I think aligns with the idea of expanding the memory attributes.
I guess this is tangentially related and could do with some profiling, but we should be careful about adding too many states in the maple tree.
Conversion iterates over offset ranges in the maple tree, and iteration is faster if there are fewer nodes in the maple tree.
Quoted 6 lines
If we just have two states (shared/private) in the maple tree, each node is either all private or all shared.
If we have more states, private ranges might get fragmented based on the orthogonal bits, e.g. RWX, which could then impact conversion performance.
I'm thinking along these same lines yea.
Quoted 11 lines
Quoted 5 lines
But I've
had some issues with the TDX tests in trying to decipher when to callvm_set_memory_attributes()vsguest_memfd_convert_private().Hope the above explanation helps!
+ Legacy dual backing:
vm_set_memory_attributes()
+ Single backing:guest_memfd_convert_private()
Quoted 6 lines
I don't think this will change much even with the module param, since userspace will still need to know whether to pass in a vm fd or a
guest_memfdfd.Or maybe
vm_set_memory_attributes()can take a vm fd, then query module params and then figure out if it should pass vm orguest_mefdfds?
For the tests that might help for sure.
Generally I'm hesitant to introduce module parameters as they are pretty big hammers to change behavior. But if it makes things easier and is acceptable then I'm not going to complain...
Quoted 32 lines
Quoted 27 lines
Quoted 19 lines
[...snip...]
Quoted 5 lines
Quoted 5 lines
static int __kvm_gmem_prepare_folio(struct kvm *kvm, struct
kvm_memory_slot*slot,(quoted diff) +0
-0pgoff_t index, struct folio *folio) {@@ -333,7 +404,7 @@ static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf)Quoted 9 lines
Review static vm_fault_t kvm_gmem_fault_shared(struct vm_fault *vmf) · line 407
Quoted 4 lines
(quoted diff) +1
-1filemap_invalidate_lock_shared(inode->i_mapping); - folio = kvm_gmem_get_folio(inode, vmf->pgoff); + folio = kvm_gmem_get_shared_folio(inode, vmf->pgoff);I am fairly certain there's a TOCTOU bug here. AFAICT, nothing prevents the underlying memory from being converted from shared=>private after checking that the page is SHARED.
Quoted 7 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.Quoted 4 lines
I don't think using the
filemap_invalidate_lock()is going to work well here. I've had some hangs on it in my testing and experiments. I think it is better to specifically lock the state tracking itself. I believe Michael mentioned this as well in a previous thread.
Quoted 6 lines
Definitely took the big hammer lock for a start and might be optimizable.
Considerations so far: when a conversion is happening, these have to be locked out:
+ Conversions from competing threads
Agreed. And this needs filemap_invalidate_lock() as well as the maple tree lock.
Call this item 1.
Quoted 2 lines
+ Allocations in
kvm_gmem_fault_user_mapping(), because whether an offset can be faulted depends on the outcome of conversion
Agreed. And this needs filemap_invalidate_lock() as well as the maple tree lock.
Call this item 2.
Quoted 6 lines
+ Allocations (
fallocate()orkvm_gmem_get_pfn()) and truncations, because conversions (for now) involves removing a folio from the filemap, restructuring, then restoring to the filemap, and
+ Allocations should reuse a folio that was already in the filemap
+ Truncations remove a folio, and should not skip removal of a folio because it was taken out just for conversion
I don't think this is required...
Quoted 3 lines
+ memory failure handling, where we don't remove folios from the filemap, but we might restructure, to split huge folios to just unmap pages with failed memory
... nor this. These don't change the sharability maple tree.
These operations don't change or need to know the shareability AFAICT.
Merging a folio would have to check the maple tree to ensure we don't merge incompatible folios... But that is a read check and should be easy to add.
Quoted 4 lines
I think essentially because conversion involves restructuring, and restructuring involves filemap operations and other filemap operations have to wait, conversion also takes the
filemap_invalidate_lock()that filemap operations use.
I could be convinced otherwise but I'm thinking the overhead of another lock for the sake of simplicity is a good trade off. I don't think any of the conversions are a fast path operation are they?
Ira
[snip]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 58 lines · Ira Weiny
[...snip...]
Quoted 14 lines
Quoted 9 lines
Quoted 8 lines
Conversions take the
filemap_invalidate_lock()too, along with allocations, truncations.Because the
filemap_invalidate_lock()might be reused for other fs-specific operations, I didn't do themt_set_external_lock()thing to lock at a low level to avoid nested locking or special maple tree code to avoid taking the lock on other paths.Quoted 4 lines
I don't think using the
filemap_invalidate_lock()is going to work well here. I've had some hangs on it in my testing and experiments. I think it is better to specifically lock the state tracking itself. I believe Michael mentioned this as well in a previous thread.Quoted 6 lines
Definitely took the big hammer lock for a start and might be optimizable.
Considerations so far: when a conversion is happening, these have to be locked out:
+ Conversions from competing threads
Agreed. And this needs
filemap_invalidate_lock()as well as the maple tree lock.Call this item 1.
Quoted 2 lines
+ Allocations in
kvm_gmem_fault_user_mapping(), because whether an offset can be faulted depends on the outcome of conversionAgreed. And this needs
filemap_invalidate_lock()as well as the maple tree lock.Call this item 2.
Quoted 6 lines
+ Allocations (
fallocate()orkvm_gmem_get_pfn()) and truncations, because conversions (for now) involves removing a folio from the filemap, restructuring, then restoring to the filemap, and
+ Allocations should reuse a folio that was already in the filemap
+ Truncations remove a folio, and should not skip removal of a folio because it was taken out just for conversionI don't think this is required...
Quoted 3 lines
+ memory failure handling, where we don't remove folios from the filemap, but we might restructure, to split huge folios to just unmap pages with failed memory
... nor this. These don't change the sharability maple tree.
These operations don't change or need to know the shareability AFAICT.
Merging a folio would have to check the maple tree to ensure we don't merge incompatible folios... But that is a read check and should be easy to add.
Quoted 8 lines
Quoted 4 lines
I think essentially because conversion involves restructuring, and restructuring involves filemap operations and other filemap operations have to wait, conversion also takes the
filemap_invalidate_lock()that filemap operations use.I could be convinced otherwise but I'm thinking the overhead of another lock for the sake of simplicity is a good trade off. I don't think any of the conversions are a fast path operation are they?
Haha, I think not having another lock is simpler! Looks like it's starting to get subjective.
For the next RFC, I'll go with re-using the filemap_invalidate_lock(), and the next RFC will have quite some changes too. Please feel free to bring this up again. The next RFC is an RFC and won't be committal anyway :)
Quoted 4 lines
Ira
[snip]
Re: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
In reply to Sean ChristophersonQuoted 15 lines · Sean Christopherson
Review
Quoted 7 lines
(quoted diff) +6
-0+}; + +enum shareability { + SHAREABILITY_GUEST = 1, /* Only the guest can map (fault) folios in this range. */ + SHAREABILITY_ALL = 2, /* Both guest and host can fault folios in this range. */ +};Rather than define new values and new KVM uAPI, I think we should instead simply support
KVM_SET_MEMORY_ATTRIBUTES. We'll probably need a new CAP, as I'm not sure supporting KVM_CHECK_EXTENSION+KVM_CAP_MEMORY_ATTRIBUTES on a gmem fd would be a good idea (e.g. trying to doKVM_CAP_GUEST_MEMFD_FLAGSon a gmem fd doesn't work because the whole point is to get flags _before_ creating the gmem instance). But adding e.g.KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTESis easy enough.
Quoted 6 lines
But for specifying PRIVATE vs. SHARED, I don't see any reason to define new uAPI. I also don't want an entirely new set of terms in KVM to describe the same things. PRIVATE and SHARED are far from perfect, but they're better than https://xkcd.com/927.
And if we ever want to let userspace restrict RWX protections in gmem, we'll have a ready-made way to do so.
I don't understand why we need to reuse KVM_SET_MEMORY_ATTRIBUTES. It anyways is a new ABI as it's on a guest_memfd FD instead of KVM FD.
RWX protections seem to be pagetable configuration rather than guest_memfd properties. Can mmap flags + kvm userfaultfd help enforce RWX protections?
Sean Christopherson
maintainerRe: [RFC PATCH v2 02/51] KVM: guest_memfd: Introduce and use shareability to guard faulting
Quoted 23 lines · Vishal Annapurve
Quoted 15 lines · Sean Christopherson
Review
Quoted 7 lines
(quoted diff) +6
-0+}; + +enum shareability { + SHAREABILITY_GUEST = 1, /* Only the guest can map (fault) folios in this range. */ + SHAREABILITY_ALL = 2, /* Both guest and host can fault folios in this range. */ +};Rather than define new values and new KVM uAPI, I think we should instead simply support
KVM_SET_MEMORY_ATTRIBUTES. We'll probably need a new CAP, as I'm not sure supporting KVM_CHECK_EXTENSION+KVM_CAP_MEMORY_ATTRIBUTES on a gmem fd would be a good idea (e.g. trying to doKVM_CAP_GUEST_MEMFD_FLAGSon a gmem fd doesn't work because the whole point is to get flags _before_ creating the gmem instance). But adding e.g.KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTESis easy enough.Quoted 6 lines
But for specifying PRIVATE vs. SHARED, I don't see any reason to define new uAPI. I also don't want an entirely new set of terms in KVM to describe the same things. PRIVATE and SHARED are far from perfect, but they're better than https://xkcd.com/927.
And if we ever want to let userspace restrict RWX protections in gmem, we'll have a ready-made way to do so.
Quoted 2 lines
I don't understand why we need to reuse
KVM_SET_MEMORY_ATTRIBUTES. It anyways is a new ABI as it's on aguest_memfdFD instead of KVM FD.
Yes, it's new functionality, but the semantics are the same (modulo s/address/offset), which makes life easier for KVM and its developers. Specifically I want to avoid ending up with two entirely different ways for describing private vs. shared memory. E.g. I don't want to have to translate between SHAREABILITY_GUEST and PRIVATE, in code or in conversation.
Quoted 3 lines
RWX protections seem to be pagetable configuration rather than
guest_memfdproperties. Can mmap flags + kvm userfaultfd help enforce RWX protections?
No, because mmap() is optional. Potential use cases are for (seletively) restricting _guest_ access as well as host access. mmap() isn't a good fit regardless, as that's much more about describing what the process wants, not the properties of the underlying memory.
E.g. read-only and noexec file systems exist for a reason.
RFC PATCH v2 3/51 KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
In reply to Ackerley TngTest that GUEST_MEMFD_FLAG_INIT_PRIVATE is only valid when GUEST_MEMFD_FLAG_SUPPORT_SHARED is set.
--- .../testing/selftests/kvm/guest_memfd_test.c | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-)
tools/testing/selftests/kvm/guest_memfd_test.c +24 -12
@@ -401,13 +401,31 @@ static void test_with_type(unsigned long vm_type, uint64_t guest_memfd_flags,kvm_vm_release (vm); } +static void test_vm_with_gmem_flag (struct kvm_vm *vm,uint64_t flag, +bool expect_valid) +{ +size_t page_size =getpagesize (); +int fd; + + fd =__vm_create_guest_memfd (vm, page_size, flag); + +if (expect_valid) { +TEST_ASSERT (fd >0 , +"guest_memfd() with flag '0x%lx' should be valid" , + flag); +close (fd); + }else { +TEST_ASSERT (fd ==-1 && errno ==EINVAL , +"guest_memfd() with flag '0x%lx' should fail with EINVAL" , + flag); + } +} +static void test_vm_type_gmem_flag_validity (unsigned long vm_type,uint64_t expected_valid_flags) { -size_t page_size =getpagesize ();struct kvm_vm *vm;uint64_t flag =0 ; -int fd;if (!(kvm_check_cap (KVM_CAP_VM_TYPES ) &BIT (vm_type)))return ;
@@ -415,17 +433,11 @@ static void test_vm_type_gmem_flag_validity(unsigned long vm_type, vm =vm_create_barebones_type (vm_type);for (flag =BIT (0 ); flag; flag <<=1 ) { - fd =__vm_create_guest_memfd (vm, page_size, flag); +test_vm_with_gmem_flag (vm, flag, flag & expected_valid_flags); -if (flag & expected_valid_flags) { -TEST_ASSERT (fd >0 , -"guest_memfd() with flag '0x%lx' should be valid" , - flag); -close (fd); - }else { -TEST_ASSERT (fd ==-1 && errno ==EINVAL , -"guest_memfd() with flag '0x%lx' should fail with EINVAL" , - flag); +if (flag ==GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { +test_vm_with_gmem_flag ( + vm, flag |GUEST_MEMFD_FLAG_INIT_PRIVATE , true); } }
Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 03/51] KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
Quoted 56 lines · Ackerley Tng
Test that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid whenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Change-Id I506e236a232047cfaee17bcaed02ee14c8d25bbbSigned-off-by Ackerley Tng <[email protected]>--- .../testing/selftests/kvm/guest_memfd_test.c | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-)tools/testing/selftests/kvm/guest_memfd_test.c +21
-5@@ -401,13 +401,31 @@ static void test_with_type(unsigned long vm_type, uint64_t guest_memfd_flags,kvm_vm_release (vm); } +static void test_vm_with_gmem_flag (struct kvm_vm *vm,uint64_t flag, +bool expect_valid) +{ +size_t page_size =getpagesize (); +int fd; + + fd =__vm_create_guest_memfd (vm, page_size, flag); + +if (expect_valid) { +TEST_ASSERT (fd >0 , +"guest_memfd() with flag '0x%lx' should be valid" , + flag); +close (fd); + }else { +TEST_ASSERT (fd ==-1 && errno ==EINVAL , +"guest_memfd() with flag '0x%lx' should fail with EINVAL" , + flag); + } +} +static void test_vm_type_gmem_flag_validity (unsigned long vm_type,uint64_t expected_valid_flags) { -size_t page_size =getpagesize ();struct kvm_vm *vm;uint64_t flag =0 ; -int fd;if (!(kvm_check_cap (KVM_CAP_VM_TYPES ) &BIT (vm_type)))return ;@@ -415,17 +433,11 @@ static void test_vm_type_gmem_flag_validity(unsigned long vm_type, vm =vm_create_barebones_type (vm_type);for (flag =BIT (0 ); flag; flag <<=1 ) { - fd =__vm_create_guest_memfd (vm, page_size, flag); +test_vm_with_gmem_flag (vm, flag, flag & expected_valid_flags); -if (flag & expected_valid_flags) { - TEST_ASSERT(fd > 0,
tools/testing/selftests/kvm/guest_memfd_test.c static void test_vm_type_gmem_flag_validity(unsigned long vm_type, · line 440
Quoted 10 lines
tools/testing/selftests/kvm/guest_memfd_test.c +3
-7-"guest_memfd() with flag '0x%lx' should be valid" , - flag); -close (fd); - }else { -TEST_ASSERT (fd ==-1 && errno ==EINVAL , -"guest_memfd() with flag '0x%lx' should fail with EINVAL" , - flag); +if (flag ==GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { +test_vm_with_gmem_flag ( + vm, flag |GUEST_MEMFD_FLAG_INIT_PRIVATE , true);
I don't understand the point of this check. In 2/51 we set GUEST_MEMFD_FLAG_INIT_PRIVATE when GUEST_MEMFD_FLAG_SUPPORT_SHARED is set.
When can this check ever fail?
Ira
Re: [RFC PATCH v2 03/51] KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
Quoted 71 lines · Ira Weiny
Quoted 56 lines · Ackerley Tng
Test that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid whenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Change-Id I506e236a232047cfaee17bcaed02ee14c8d25bbbSigned-off-by Ackerley Tng <[email protected]>--- .../testing/selftests/kvm/guest_memfd_test.c | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-)tools/testing/selftests/kvm/guest_memfd_test.c +21
-5@@ -401,13 +401,31 @@ static void test_with_type(unsigned long vm_type, uint64_t guest_memfd_flags,kvm_vm_release (vm); } +static void test_vm_with_gmem_flag (struct kvm_vm *vm,uint64_t flag, +bool expect_valid) +{ +size_t page_size =getpagesize (); +int fd; + + fd =__vm_create_guest_memfd (vm, page_size, flag); + +if (expect_valid) { +TEST_ASSERT (fd >0 , +"guest_memfd() with flag '0x%lx' should be valid" , + flag); +close (fd); + }else { +TEST_ASSERT (fd ==-1 && errno ==EINVAL , +"guest_memfd() with flag '0x%lx' should fail with EINVAL" , + flag); + } +} +static void test_vm_type_gmem_flag_validity (unsigned long vm_type,uint64_t expected_valid_flags) { -size_t page_size =getpagesize ();struct kvm_vm *vm;uint64_t flag =0 ; -int fd;if (!(kvm_check_cap (KVM_CAP_VM_TYPES ) &BIT (vm_type)))return ;@@ -415,17 +433,11 @@ static void test_vm_type_gmem_flag_validity(unsigned long vm_type, vm =vm_create_barebones_type (vm_type);for (flag =BIT (0 ); flag; flag <<=1 ) { - fd =__vm_create_guest_memfd (vm, page_size, flag); +test_vm_with_gmem_flag (vm, flag, flag & expected_valid_flags); -if (flag & expected_valid_flags) { - TEST_ASSERT(fd > 0,tools/testing/selftests/kvm/guest_memfd_test.c static void test_vm_type_gmem_flag_validity(unsigned long vm_type, · line 440
Quoted 10 lines
tools/testing/selftests/kvm/guest_memfd_test.c +3
-7-"guest_memfd() with flag '0x%lx' should be valid" , - flag); -close (fd); - }else { -TEST_ASSERT (fd ==-1 && errno ==EINVAL , -"guest_memfd() with flag '0x%lx' should fail with EINVAL" , - flag); +if (flag ==GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { +test_vm_with_gmem_flag ( + vm, flag |GUEST_MEMFD_FLAG_INIT_PRIVATE , true);I don't understand the point of this check. In 2/51 we set
GUEST_MEMFD_FLAG_INIT_PRIVATEwhenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.
Quoted 3 lines
When can this check ever fail?
Ira
In 02/51, GUEST_MEMFD_FLAG_INIT_PRIVATE is not set by default, GUEST_MEMFD_FLAG_INIT_PRIVATE is set as one of the valid_flags.
The intention is that GUEST_MEMFD_FLAG_INIT_PRIVATE is only valid if GUEST_MEMFD_FLAG_SUPPORT_SHARED is requested by userspace.
In this test, the earlier part before the if block calls test_vm_with_gmem_flag() all valid flags, and that already tests GUEST_MEMFD_FLAG_SUPPORT_SHARED individually.
Specifically if GUEST_MEMFD_FLAG_SUPPORT_SHARED is set, this if block adds a test for when both GUEST_MEMFD_FLAG_SUPPORT_SHARED and GUEST_MEMFD_FLAG_INIT_PRIVATE are set, and sets that expect_valid is true.
This second test doesn't fail, it is meant to check that the kernel allows the pair of flags to be set. Hope that makes sense.
Re: [RFC PATCH v2 03/51] KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
Quoted 73 lines · Ackerley Tng
Quoted 71 lines · Ira Weiny
Quoted 56 lines · Ackerley Tng
Test that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid whenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Change-Id I506e236a232047cfaee17bcaed02ee14c8d25bbbSigned-off-by Ackerley Tng <[email protected]>--- .../testing/selftests/kvm/guest_memfd_test.c | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-)tools/testing/selftests/kvm/guest_memfd_test.c +21
-5@@ -401,13 +401,31 @@ static void test_with_type(unsigned long vm_type, uint64_t guest_memfd_flags,kvm_vm_release (vm); } +static void test_vm_with_gmem_flag (struct kvm_vm *vm,uint64_t flag, +bool expect_valid) +{ +size_t page_size =getpagesize (); +int fd; + + fd =__vm_create_guest_memfd (vm, page_size, flag); + +if (expect_valid) { +TEST_ASSERT (fd >0 , +"guest_memfd() with flag '0x%lx' should be valid" , + flag); +close (fd); + }else { +TEST_ASSERT (fd ==-1 && errno ==EINVAL , +"guest_memfd() with flag '0x%lx' should fail with EINVAL" , + flag); + } +} +static void test_vm_type_gmem_flag_validity (unsigned long vm_type,uint64_t expected_valid_flags) { -size_t page_size =getpagesize ();struct kvm_vm *vm;uint64_t flag =0 ; -int fd;if (!(kvm_check_cap (KVM_CAP_VM_TYPES ) &BIT (vm_type)))return ;@@ -415,17 +433,11 @@ static void test_vm_type_gmem_flag_validity(unsigned long vm_type, vm =vm_create_barebones_type (vm_type);for (flag =BIT (0 ); flag; flag <<=1 ) { - fd =__vm_create_guest_memfd (vm, page_size, flag); +test_vm_with_gmem_flag (vm, flag, flag & expected_valid_flags); -if (flag & expected_valid_flags) { - TEST_ASSERT(fd > 0,tools/testing/selftests/kvm/guest_memfd_test.c static void test_vm_type_gmem_flag_validity(unsigned long vm_type, · line 440
Quoted 10 lines
tools/testing/selftests/kvm/guest_memfd_test.c +3
-7-"guest_memfd() with flag '0x%lx' should be valid" , - flag); -close (fd); - }else { -TEST_ASSERT (fd ==-1 && errno ==EINVAL , -"guest_memfd() with flag '0x%lx' should fail with EINVAL" , - flag); +if (flag ==GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { +test_vm_with_gmem_flag ( + vm, flag |GUEST_MEMFD_FLAG_INIT_PRIVATE , true);I don't understand the point of this check. In 2/51 we set
GUEST_MEMFD_FLAG_INIT_PRIVATEwhenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.
Quoted 6 lines
Quoted 3 lines
When can this check ever fail?
Ira
In 02/51,
GUEST_MEMFD_FLAG_INIT_PRIVATEis not set by default,GUEST_MEMFD_FLAG_INIT_PRIVATEis set as one of thevalid_flags.
Ah My mistake I read that too quickly.
Thanks,
Ira
Quoted 15 lines
The intention is that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid ifGUEST_MEMFD_FLAG_SUPPORT_SHAREDis requested by userspace.In this test, the earlier part before the if block calls
test_vm_with_gmem_flag()all valid flags, and that already testsGUEST_MEMFD_FLAG_SUPPORT_SHAREDindividually.Specifically if
GUEST_MEMFD_FLAG_SUPPORT_SHAREDis set, this if block adds a test for when bothGUEST_MEMFD_FLAG_SUPPORT_SHAREDandGUEST_MEMFD_FLAG_INIT_PRIVATEare set, and sets thatexpect_validis true.This second test doesn't fail, it is meant to check that the kernel allows the pair of flags to be set. Hope that makes sense.
Re: [RFC PATCH v2 03/51] KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
In reply to Ackerley TngQuoted 81 lines · Ackerley Tng
Quoted 70 lines · Ira Weiny
Quoted 56 lines · Ackerley Tng
Test that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid whenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Change-Id I506e236a232047cfaee17bcaed02ee14c8d25bbbSigned-off-by Ackerley Tng <[email protected]>--- .../testing/selftests/kvm/guest_memfd_test.c | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-)tools/testing/selftests/kvm/guest_memfd_test.c +21
-5@@ -401,13 +401,31 @@ static void test_with_type(unsigned long vm_type, uint64_t guest_memfd_flags,kvm_vm_release (vm); } +static void test_vm_with_gmem_flag (struct kvm_vm *vm,uint64_t flag, +bool expect_valid) +{ +size_t page_size =getpagesize (); +int fd; + + fd =__vm_create_guest_memfd (vm, page_size, flag); + +if (expect_valid) { +TEST_ASSERT (fd >0 , +"guest_memfd() with flag '0x%lx' should be valid" , + flag); +close (fd); + }else { +TEST_ASSERT (fd ==-1 && errno ==EINVAL , +"guest_memfd() with flag '0x%lx' should fail with EINVAL" , + flag); + } +} +static void test_vm_type_gmem_flag_validity (unsigned long vm_type,uint64_t expected_valid_flags) { -size_t page_size =getpagesize ();struct kvm_vm *vm;uint64_t flag =0 ; -int fd;if (!(kvm_check_cap (KVM_CAP_VM_TYPES ) &BIT (vm_type)))return ;@@ -415,17 +433,11 @@ static void test_vm_type_gmem_flag_validity(unsigned long vm_type, vm =vm_create_barebones_type (vm_type);for (flag =BIT (0 ); flag; flag <<=1 ) { - fd =__vm_create_guest_memfd (vm, page_size, flag); +test_vm_with_gmem_flag (vm, flag, flag & expected_valid_flags); -if (flag & expected_valid_flags) { - TEST_ASSERT(fd > 0,tools/testing/selftests/kvm/guest_memfd_test.c static void test_vm_type_gmem_flag_validity(unsigned long vm_type, · line 440
Quoted 10 lines
tools/testing/selftests/kvm/guest_memfd_test.c +3
-7-"guest_memfd() with flag '0x%lx' should be valid" , - flag); -close (fd); - }else { -TEST_ASSERT (fd ==-1 && errno ==EINVAL , -"guest_memfd() with flag '0x%lx' should fail with EINVAL" , - flag); +if (flag ==GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { +test_vm_with_gmem_flag ( + vm, flag |GUEST_MEMFD_FLAG_INIT_PRIVATE , true);I don't understand the point of this check. In 2/51 we set
GUEST_MEMFD_FLAG_INIT_PRIVATEwhenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Quoted 3 lines
When can this check ever fail?
Ira
In 02/51,
GUEST_MEMFD_FLAG_INIT_PRIVATEis not set by default,GUEST_MEMFD_FLAG_INIT_PRIVATEis set as one of thevalid_flags.The intention is that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid ifGUEST_MEMFD_FLAG_SUPPORT_SHAREDis requested by userspace.
Quoted 8 lines
In this test, the earlier part before the if block calls
test_vm_with_gmem_flag()all valid flags, and that already testsGUEST_MEMFD_FLAG_SUPPORT_SHAREDindividually.Specifically if
GUEST_MEMFD_FLAG_SUPPORT_SHAREDis set, this if block adds a test for when bothGUEST_MEMFD_FLAG_SUPPORT_SHAREDandGUEST_MEMFD_FLAG_INIT_PRIVATEare set, and sets thatexpect_validis true.
Maybe it's more clear to move this case out of the loop?
Quoted 3 lines
This second test doesn't fail, it is meant to check that the kernel allows the pair of flags to be set. Hope that makes sense.
Re: [RFC PATCH v2 03/51] KVM: selftests: Update guest_memfd_test for INIT_PRIVATE flag
Quoted 86 lines · Binbin Wu
Quoted 85 lines · Ackerley Tng
Quoted 70 lines · Ira Weiny
Quoted 56 lines · Ackerley Tng
Test that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid whenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Change-Id I506e236a232047cfaee17bcaed02ee14c8d25bbbSigned-off-by Ackerley Tng <[email protected]>--- .../testing/selftests/kvm/guest_memfd_test.c | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-)tools/testing/selftests/kvm/guest_memfd_test.c +21
-5@@ -401,13 +401,31 @@ static void test_with_type(unsigned long vm_type, uint64_t guest_memfd_flags,kvm_vm_release (vm); } +static void test_vm_with_gmem_flag (struct kvm_vm *vm,uint64_t flag, +bool expect_valid) +{ +size_t page_size =getpagesize (); +int fd; + + fd =__vm_create_guest_memfd (vm, page_size, flag); + +if (expect_valid) { +TEST_ASSERT (fd >0 , +"guest_memfd() with flag '0x%lx' should be valid" , + flag); +close (fd); + }else { +TEST_ASSERT (fd ==-1 && errno ==EINVAL , +"guest_memfd() with flag '0x%lx' should fail with EINVAL" , + flag); + } +} +static void test_vm_type_gmem_flag_validity (unsigned long vm_type,uint64_t expected_valid_flags) { -size_t page_size =getpagesize ();struct kvm_vm *vm;uint64_t flag =0 ; -int fd;if (!(kvm_check_cap (KVM_CAP_VM_TYPES ) &BIT (vm_type)))return ;@@ -415,17 +433,11 @@ static void test_vm_type_gmem_flag_validity(unsigned long vm_type, vm =vm_create_barebones_type (vm_type);for (flag =BIT (0 ); flag; flag <<=1 ) { - fd =__vm_create_guest_memfd (vm, page_size, flag); +test_vm_with_gmem_flag (vm, flag, flag & expected_valid_flags); -if (flag & expected_valid_flags) { - TEST_ASSERT(fd > 0,tools/testing/selftests/kvm/guest_memfd_test.c static void test_vm_type_gmem_flag_validity(unsigned long vm_type, · line 440
Quoted 10 lines
tools/testing/selftests/kvm/guest_memfd_test.c +3
-7-"guest_memfd() with flag '0x%lx' should be valid" , - flag); -close (fd); - }else { -TEST_ASSERT (fd ==-1 && errno ==EINVAL , -"guest_memfd() with flag '0x%lx' should fail with EINVAL" , - flag); +if (flag ==GUEST_MEMFD_FLAG_SUPPORT_SHARED ) { +test_vm_with_gmem_flag ( + vm, flag |GUEST_MEMFD_FLAG_INIT_PRIVATE , true);I don't understand the point of this check. In 2/51 we set
GUEST_MEMFD_FLAG_INIT_PRIVATEwhenGUEST_MEMFD_FLAG_SUPPORT_SHAREDis set.Quoted 3 lines
When can this check ever fail?
Ira
In 02/51,
GUEST_MEMFD_FLAG_INIT_PRIVATEis not set by default,GUEST_MEMFD_FLAG_INIT_PRIVATEis set as one of thevalid_flags.The intention is that
GUEST_MEMFD_FLAG_INIT_PRIVATEis only valid ifGUEST_MEMFD_FLAG_SUPPORT_SHAREDis requested by userspace.In this test, the earlier part before the if block calls
test_vm_with_gmem_flag()all valid flags, and that already testsGUEST_MEMFD_FLAG_SUPPORT_SHAREDindividually.
Quoted 6 lines
Quoted 4 lines
Specifically if
GUEST_MEMFD_FLAG_SUPPORT_SHAREDis set, this if block adds a test for when bothGUEST_MEMFD_FLAG_SUPPORT_SHAREDandGUEST_MEMFD_FLAG_INIT_PRIVATEare set, and sets thatexpect_validis true.Maybe it's more clear to move this case out of the loop?
Will try that in the next revision. Thanks!
Quoted 3 lines
Quoted 3 lines
This second test doesn't fail, it is meant to check that the kernel allows the pair of flags to be set. Hope that makes sense.
RFC PATCH v2 4/51 KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
In reply to Ackerley TngThe two new guest_memfd ioctls KVM_GMEM_CONVERT_SHARED and KVM_GMEM_CONVERT_PRIVATE convert the requested memory ranges to shared and private respectively.
A guest_memfd ioctl is used because shareability is a property of the memory, and this property should be modifiable independently of the attached struct kvm. This allows shareability to be modified even if the memory is not yet bound using memslots.
For shared to private conversions, if refcounts on any of the folios within the range are elevated, fail the conversion with -EAGAIN.
At the point of shared to private conversion, all folios in range are also unmapped. The filemap_invalidate_lock() is held, so no faulting can occur. Hence, from that point on, only transient refcounts can be taken on the folios associated with that guest_memfd.
Hence, it is safe to do the conversion from shared to private.
After conversion is complete, refcounts may become elevated, but that is fine since users of transient refcounts don't actually access memory.
For private to shared conversions, there are no refcount checks. any transient refcounts are expected to drop their refcounts soon. The conversion process will spin waiting for these transient refcounts to go away.
Change-Id: I3546aaf6c1b795de6dc9ba09e816b64934221918
--- include/uapi/linux/kvm.h | 11 ++ virt/kvm/guest_memfd.c | 357 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 366 insertions(+), 2 deletions(-)
include/uapi/linux/kvm.h +11 -0
@@ -1577,6 +1577,17 @@ struct kvm_create_guest_memfd {__u64 reserved[6 ]; }; +#define KVM_GMEM_IO 0xAF +#define KVM_GMEM_CONVERT_SHARED _IOWR(KVM_GMEM_IO, 0x41, struct kvm_gmem_convert) +#define KVM_GMEM_CONVERT_PRIVATE _IOWR(KVM_GMEM_IO, 0x42, struct kvm_gmem_convert) + +struct kvm_gmem_convert { +__u64 offset; +__u64 size; +__u64 error_offset; +__u64 reserved[5 ]; +}; +#define KVM_PRE_FAULT_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)struct kvm_pre_fault_memory {
virt/kvm/guest_memfd.c +355 -2
393 lines not on this page · raw message
Signature · 1 line
2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Ackerley Tng wrote:
[snip]
Quoted 2 lines
Signed-off-by Ackerley Tng <[email protected]>
[snip]
Quoted 19 lines
virt/kvm/guest_memfd.c +4
-0@@ -30,6 +30,10 @@ enum shareability { };static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); +static void kvm_gmem_invalidate_begin (struct kvm_gmem *gmem,pgoff_t start, +pgoff_t end); +static void kvm_gmem_invalidate_end (struct kvm_gmem *gmem,pgoff_t start, +pgoff_t end);static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) {@@ -85,6 +89,306 @@ static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t indereturn kvm_gmem_get_folio (inode, index); }
virt/kvm/guest_memfd.c static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t inde · line 101
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+/** +* kvm_gmem_shareability_store() - Sets shareability to @value for range. +* +* @mt: the shareability maple tree. +* @index: the range begins at this index in the inode. +* @nr_pages: number of PAGE_SIZE pages in this range. +* @value: the shareability value to set for this range. +* +* Unlike mtree_store_range(), this function also merges adjacent ranges that +* have the same values as an optimization.
Is this an optimization or something which will be required to convert from shared back to private and back to a huge page mapping?
If this is purely an optimization it might be best to leave it out for now to get functionality first.
I have more to review but wanted to ask this.
Ira
[snip]
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 28 lines · Ira Weiny
Ackerley Tng wrote:
[snip]
Quoted 2 lines
Signed-off-by Ackerley Tng <[email protected]>[snip]
Quoted 19 lines
virt/kvm/guest_memfd.c +4
-0@@ -30,6 +30,10 @@ enum shareability { };static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); +static void kvm_gmem_invalidate_begin (struct kvm_gmem *gmem,pgoff_t start, +pgoff_t end); +static void kvm_gmem_invalidate_end (struct kvm_gmem *gmem,pgoff_t start, +pgoff_t end);static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) {@@ -85,6 +89,306 @@ static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t indereturn kvm_gmem_get_folio (inode, index); }
Quoted 14 lines
virt/kvm/guest_memfd.c static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t inde
Quoted 10 lines
virt/kvm/guest_memfd.c +10
-0+/** +* kvm_gmem_shareability_store() - Sets shareability to @value for range. +* +* @mt: the shareability maple tree. +* @index: the range begins at this index in the inode. +* @nr_pages: number of PAGE_SIZE pages in this range. +* @value: the shareability value to set for this range. +* +* Unlike mtree_store_range(), this function also merges adjacent ranges that +* have the same values as an optimization. Is this an optimization or something which will be required to convert from shared back to private and back to a huge page mapping?
This is an optimization.
Quoted 3 lines
If this is purely an optimization it might be best to leave it out for now to get functionality first.
I see this (small) optimization as part of using maple trees.
Fuad's version [1] uses xarrays and has 1 xarray entry per page offset. I wanted to illustrate that by using maple trees, we can share just 1 entry for a whole range, and part of that sharing involves merging adjacent shareability entries that have the same value.
IIUC, these other users of maple trees also do some kind of expansion/range merging:
+ VMAs in vma_expand() [2]
+ regcache in regcache_maple_write() [3]
Quoted 5 lines
I have more to review but wanted to ask this.
Ira
[snip]
[1] https://lore.kernel.org/all/[email protected]/
[2] https://elixir.bootlin.com/linux/v6.14.6/source/mm/vma.c#L1059
[3] https://elixir.bootlin.com/linux/v6.14.6/source/drivers/base/regmap/regcache-maple.c#L38
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
In reply to Ackerley TngHi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.
I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
- The host tries a VCPU run again, and the above repeats, i.e., KVM checks the refcount, finds that the host still holds references, doesn't fault the folio into the guest, and exits back to the host.
- Eventually a well-behaving host releases all its references, and the following VCPU run is able to fault the page into the guest, and proceed with running it.
In case the guest is destroyed before that happens, we have the whole folio_put() callback scenario we had discussed earlier.
In other words, the interface that I had in mind where KVM run exists (hyp call, memory fault), as well as VCPU run. Both which already exist, and convey the same information. Is there a case where that isn't enough or suboptimal?
Thanks,
/fuad
(*) An alternative suggestion was to block the VCPU from running altogether, regardless of whether it wants to fault the unshared page immediately, and continually exit to the host until references are dropped and the conversion can happen.
[1] https://lpc.events/event/17/contributions/1487/
[2] https://lpc.events/event/18/contributions/1758/
[3] https://lore.kernel.org/all/[email protected]/
[4] https://lpc.events/event/18/contributions/1758/attachments/1457/3699/Guestmemfd%20folio%20state%20page_type.pdf
Quoted 458 lines
A
guest_memfdioctl is used because shareability is a property of the memory, and this property should be modifiable independently of the attached struct kvm. This allows shareability to be modified even if the memory is not yet bound using memslots.For shared to private conversions, if refcounts on any of the folios within the range are elevated, fail the conversion with
-EAGAIN.At the point of shared to private conversion, all folios in range are also unmapped. The
filemap_invalidate_lock()is held, so no faulting can occur. Hence, from that point on, only transient refcounts can be taken on the folios associated with thatguest_memfd.Hence, it is safe to do the conversion from shared to private.
After conversion is complete, refcounts may become elevated, but that is fine since users of transient refcounts don't actually access memory.
For private to shared conversions, there are no refcount checks. any transient refcounts are expected to drop their refcounts soon. The conversion process will spin waiting for these transient refcounts to go away.
Signed-off-by Ackerley Tng <[email protected]>Change-Id: I3546aaf6c1b795de6dc9ba09e816b64934221918
--- include/uapi/linux/kvm.h | 11 ++ virt/kvm/guest_memfd.c | 357 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 366 insertions(+), 2 deletions(-)include/uapi/linux/kvm.h +11
-0@@ -1577,6 +1577,17 @@ struct kvm_create_guest_memfd {__u64 reserved[6 ]; }; +#define KVM_GMEM_IO 0xAF +#define KVM_GMEM_CONVERT_SHARED _IOWR(KVM_GMEM_IO, 0x41, struct kvm_gmem_convert) +#define KVM_GMEM_CONVERT_PRIVATE _IOWR(KVM_GMEM_IO, 0x42, struct kvm_gmem_convert) + +struct kvm_gmem_convert { +__u64 offset; +__u64 size; +__u64 error_offset; +__u64 reserved[5 ]; +}; +#define KVM_PRE_FAULT_MEMORY _IOWR(KVMIO, 0xd5, struct kvm_pre_fault_memory)struct kvm_pre_fault_memory {virt/kvm/guest_memfd.c +355
-3@@ -30,6 +30,10 @@ enum shareability { };static struct folio *kvm_gmem_get_folio (struct inode *inode,pgoff_t index); +static void kvm_gmem_invalidate_begin (struct kvm_gmem *gmem,pgoff_t start, +pgoff_t end); +static void kvm_gmem_invalidate_end (struct kvm_gmem *gmem,pgoff_t start, +pgoff_t end);static struct kvm_gmem_inode_private *kvm_gmem_private (struct inode *inode) {@@ -85,6 +89,306 @@ static struct folio *kvm_gmem_get_shared_folio(struct inode *inode, pgoff_t inde return kvm_gmem_get_folio(inode, index); } +/** +* kvm_gmem_shareability_store() - Sets shareability to @value for range. +* +* @mt: the shareability maple tree. +* @index: the range begins at this index in the inode. +* @nr_pages: number of PAGE_SIZE pages in this range. +* @value: the shareability value to set for this range. +* +* Unlike mtree_store_range(), this function also merges adjacent ranges that +* have the same values as an optimization. Assumes that all stores to @mt go +* through this function, such that adjacent ranges are always merged. +* +* Return: 0 on success and negative error otherwise. +*/ +static int kvm_gmem_shareability_store (struct maple_tree *mt,pgoff_t index, +size_t nr_pages,enum shareability value) +{ +MA_STATE (mas, mt,0 ,0 ); +unsigned long start; +unsigned long last; +void *entry; +int ret; + + start = index; + last = start + nr_pages -1 ; + +mas_lock (&mas); + +/* Try extending range. entry is NULL on overflow/wrap-around. */ +mas_set_range (&mas, last +1 , last +1 ); + entry =mas_find (&mas, last +1 ); +if (entry &&xa_to_value (entry) == value) + last = mas.last; + +mas_set_range (&mas, start -1 , start -1 ); + entry =mas_find (&mas, start -1 ); +if (entry &&xa_to_value (entry) == value) + start = mas.index; + +mas_set_range (&mas, start, last); + ret =mas_store_gfp (&mas,xa_mk_value (value),GFP_KERNEL ); + +mas_unlock (&mas); + +return ret; +} + +struct conversion_work { +struct list_head list; +pgoff_t start; +size_t nr_pages; +}; + +static int add_to_work_list (struct list_head *list,pgoff_t start,pgoff_t last) +{ +struct conversion_work *work; + + work =kzalloc (sizeof (*work),GFP_KERNEL ); +if (!work) +return -ENOMEM ; + + work->start = start; + work->nr_pages = last +1 - start; + +list_add_tail (&work->list, list); + +return 0 ; +} + +static bool kvm_gmem_has_safe_refcount (struct address_space *mapping,pgoff_t start, +size_t nr_pages,pgoff_t *error_index) +{ +const int filemap_get_folios_refcount =1 ; +struct folio_batch fbatch; +bool refcount_safe; +pgoff_t last; +int i; + + last = start + nr_pages -1 ; + refcount_safe = true; + +folio_batch_init (&fbatch); +while (refcount_safe && +filemap_get_folios (mapping, &start, last, &fbatch)) { + +for (i =0 ; i <folio_batch_count (&fbatch); ++i) { +int filemap_refcount; +int safe_refcount; +struct folio *f; + + f = fbatch.folios[i]; + filemap_refcount =folio_nr_pages (f); + + safe_refcount = filemap_refcount + filemap_get_folios_refcount; +if (folio_ref_count (f) != safe_refcount) { + refcount_safe = false; + *error_index = f->index; +break ; + } + } + +folio_batch_release (&fbatch); + } + +return refcount_safe; +} + +static int kvm_gmem_shareability_apply (struct inode *inode, +struct conversion_work *work, +enum shareability m) +{ +struct maple_tree *mt; + + mt = &kvm_gmem_private (inode)->shareability; +return kvm_gmem_shareability_store (mt, work->start, work->nr_pages, m); +} + +static int kvm_gmem_convert_compute_work (struct inode *inode,pgoff_t start, +size_t nr_pages,enum shareability m, +struct list_head *work_list) +{ +struct maple_tree *mt; +struct ma_state mas; +pgoff_t last; +void *entry; +int ret; + + last = start + nr_pages -1 ; + + mt = &kvm_gmem_private (inode)->shareability; + ret =0 ; + +mas_init (&mas, mt, start); + +rcu_read_lock (); +mas_for_each (&mas, entry, last) { +enum shareability current_m; +pgoff_t m_range_index; +pgoff_t m_range_last; +int ret; + + m_range_index =max (mas.index, start); + m_range_last =min (mas.last, last); + + current_m =xa_to_value (entry); +if (m == current_m) +continue ; + +mas_pause (&mas); +rcu_read_unlock (); +/* Caller will clean this up on error. */ + ret =add_to_work_list (work_list, m_range_index, m_range_last); +rcu_read_lock (); +if (ret) +break ; + } +rcu_read_unlock (); + +return ret; +} + +static void kvm_gmem_convert_invalidate_begin (struct inode *inode, +struct conversion_work *work) +{ +struct list_head *gmem_list; +struct kvm_gmem *gmem; +pgoff_t end; + + end = work->start + work->nr_pages; + + gmem_list = &inode->i_mapping->i_private_list; +list_for_each_entry (gmem, gmem_list, entry) +kvm_gmem_invalidate_begin (gmem, work->start, end); +} + +static void kvm_gmem_convert_invalidate_end (struct inode *inode, +struct conversion_work *work) +{ +struct list_head *gmem_list; +struct kvm_gmem *gmem; +pgoff_t end; + + end = work->start + work->nr_pages; + + gmem_list = &inode->i_mapping->i_private_list; +list_for_each_entry (gmem, gmem_list, entry) +kvm_gmem_invalidate_end (gmem, work->start, end); +} + +static int kvm_gmem_convert_should_proceed (struct inode *inode, +struct conversion_work *work, +bool to_shared,pgoff_t *error_index) +{ +if (!to_shared) { +unmap_mapping_pages (inode->i_mapping, work->start, + work->nr_pages, false); + +if (!kvm_gmem_has_safe_refcount (inode->i_mapping, work->start, + work->nr_pages, error_index)) { +return -EAGAIN ; + } + } + +return 0 ; +} + +static int kvm_gmem_convert_range (struct file *file,pgoff_t start, +size_t nr_pages,bool shared, +pgoff_t *error_index) +{ +struct conversion_work *work, *tmp, *rollback_stop_item; +LIST_HEAD (work_list); +struct inode *inode; +enum shareability m; +int ret; + + inode =file_inode (file); + +filemap_invalidate_lock (inode->i_mapping); + + m = shared ?SHAREABILITY_ALL :SHAREABILITY_GUEST ; + ret =kvm_gmem_convert_compute_work (inode, start, nr_pages, m, &work_list); +if (ret ||list_empty (&work_list)) + goto out; + +list_for_each_entry (work, &work_list, list) +kvm_gmem_convert_invalidate_begin (inode, work); + +list_for_each_entry (work, &work_list, list) { + ret =kvm_gmem_convert_should_proceed (inode, work, shared, + error_index); +if (ret) + goto invalidate_end; + } + +list_for_each_entry (work, &work_list, list) { + rollback_stop_item = work; + ret =kvm_gmem_shareability_apply (inode, work, m); +if (ret) +break ; + } + +if (ret) { + m = shared ?SHAREABILITY_GUEST :SHAREABILITY_ALL ; +list_for_each_entry (work, &work_list, list) { +if (work == rollback_stop_item) +break ; + +WARN_ON (kvm_gmem_shareability_apply (inode, work, m)); + } + } + +invalidate_end: +list_for_each_entry (work, &work_list, list) +kvm_gmem_convert_invalidate_end (inode, work); +out: +filemap_invalidate_unlock (inode->i_mapping); + +list_for_each_entry_safe (work, tmp, &work_list, list) { +list_del (&work->list); +kfree (work); + } + +return ret; +} + +static int kvm_gmem_ioctl_convert_range (struct file *file, +struct kvm_gmem_convert *param, +bool shared) +{ +pgoff_t error_index; +size_t nr_pages; +pgoff_t start; +int ret; + +if (param->error_offset) +return -EINVAL ; + +if (param->size ==0 ) +return 0 ; + +if (param->offset + param->size < param->offset || + param->offset >file_inode (file)->i_size || + param->offset + param->size >file_inode (file)->i_size) +return -EINVAL ; + +if (!IS_ALIGNED (param->offset,PAGE_SIZE ) || + !IS_ALIGNED (param->size,PAGE_SIZE )) +return -EINVAL ; + + start = param->offset >>PAGE_SHIFT ; + nr_pages = param->size >>PAGE_SHIFT ; + + ret =kvm_gmem_convert_range (file, start, nr_pages, shared, &error_index); +if (ret) + param->error_offset = error_index <<PAGE_SHIFT ; + +return ret; +} +#else static int kvm_gmem_shareability_setup (struct maple_tree *mt,loff_t size,u64 flags)@@ -186,15 +490,26 @@ static void kvm_gmem_invalidate_begin(struct kvm_gmem *gmem, pgoff_t start,unsigned long index;xa_for_each_range (&gmem->bindings, index, slot, start, end -1 ) { +enum kvm_gfn_range_filter filter;pgoff_t pgoff = slot->gmem.pgoff; + filter =KVM_FILTER_PRIVATE ; +if (kvm_gmem_memslot_supports_shared (slot)) { +/* +* Unmapping would also cause invalidation, but cannot +* rely on mmu_notifiers to do invalidation via +* unmapping, since memory may not be mapped to +* userspace. +*/ + filter |=KVM_FILTER_SHARED ; + } +struct kvm_gfn_range gfn_range = { .start = slot->base_gfn +max (pgoff, start) - pgoff, .end = slot->base_gfn +min (pgoff + slot->npages, end) - pgoff, .slot = slot, .may_block = true, -/* guest memfd is relevant to only private mappings. */ - .attr_filter =KVM_FILTER_PRIVATE , + .attr_filter = filter, };if (!found_memslot) {@@ -484,11 +799,49 @@ EXPORT_SYMBOL_GPL(kvm_gmem_memslot_supports_shared);#define kvm_gmem_mmap NULL#endif /* CONFIG_KVM_GMEM_SHARED_MEM */ +static long kvm_gmem_ioctl (struct file *file,unsigned int ioctl, +unsigned long arg) +{ +void __user *argp; +int r; + + argp = (void __user *)arg; + +switch (ioctl) { +#ifdef CONFIG_KVM_GMEM_SHARED_MEM +case KVM_GMEM_CONVERT_SHARED : +case KVM_GMEM_CONVERT_PRIVATE : { +struct kvm_gmem_convert param; +bool to_shared; + + r = -EFAULT ; +if (copy_from_user (¶m, argp,sizeof (param))) + goto out; + + to_shared = ioctl ==KVM_GMEM_CONVERT_SHARED ; + r =kvm_gmem_ioctl_convert_range (file, ¶m, to_shared); +if (r) { +if (copy_to_user (argp, ¶m,sizeof (param))) { + r = -EFAULT ; + goto out; + } + } +break ; + } +#endif +default : + r = -ENOTTY ; + } +out: +return r; +} +static struct file_operations kvm_gmem_fops = { .mmap = kvm_gmem_mmap, .open = generic_file_open, .release = kvm_gmem_release, .fallocate = kvm_gmem_fallocate, + .unlocked_ioctl = kvm_gmem_ioctl, };static void kvm_gmem_free_inode (struct inode *inode) --2.49.0.1045.g170613ef41-goog
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Are you talking about the following scheme?
1) guest_memfd checks shareability on each get pfn and if there is a mismatch exit to the host.
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
3) guest_memfd will maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.
If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
* guest_memfd will not have to deal with this extra state from 3 above and overall guest_memfd conversion handling becomes relatively simpler.
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use guest_memfd ioctl to do the same.
- Allows not having to keep track of separate shared/private range
information in KVM.
- Simpler handling of the conversion process done per guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling in guest_memfd.
- guest_memfd is single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications from guest_memfd directly and will get notified for invalidation upon shareability attribute updates.
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which get_pfn? Are you referring to get_pfn when faulting the page into the guest or into the host?
Quoted 2 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.
That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Quoted 3 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.
The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
Quoted 2 lines
- Allows not having to keep track of separate shared/private range
information in KVM.
This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.
All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.
Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?
I am referring to guest fault handling in KVM.
Quoted 23 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.
Quoted 2 lines
That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Who is *we* here? Which entity will retry conversion?
Quoted 9 lines
Quoted 4 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the guest_memfd pages for the range being converted.
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for guest_memfd pages? Can you help walk an example sequence for shared to private conversion specifically around guest_memfd offset states?
Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Vishal,
Quoted 96 lines · Vishal Annapurve
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?I am referring to guest fault handling in KVM.
Quoted 23 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.
Quoted 4 lines
Quoted 2 lines
That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Who is *we* here? Which entity will retry conversion?
Userspace will re-attempt the VCPU run.
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Quoted 5 lines
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the
guest_memfdpages for the range being converted.
Yes. Userspace will know that it needs to do that on the VCPU exit, which informs it of the guest's hypervisor request to unshare (convert from shared to private) the page.
Quoted 4 lines
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
How can the guest erroneously try to take away memory? If the guest sends a hypervisor request asking for a conversion of memory that doesn't belong to it, then I would expect the hypervisor to prevent that.
I don't see how having an IOCTL to trigger the conversion is needed to allow conversion failure. How is that different from userspace ignoring or delaying releasing all references it has for the conversion request?
Quoted 9 lines
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for
guest_memfdpages? Can you help walk an example sequence for shared to private conversion specifically aroundguest_memfdoffset states?
To make sure that we are discussing the same scenario: can you do the same as well please --- walk me through an example sequence for shared to private conversion specifically around guest_memfd offset states With the IOCTLs involved?
Here is an example that I have implemented and tested with pKVM. Note that there are alternatives, the flow below is architecture or even vm-type dependent. None of this code is code KVM code and the behaviour could vary.
Assuming the folio is shared with the host:
Guest sends unshare hypercall to the hypervisor
Hypervisor forwards request to KVM (gmem) (having done due diligence) KVM (gmem) performs an unmap_folio(), exits to userspace with KVM_EXIT_UNSHARE and all the information about the folio being unshared
Case 1:
Userspace removes any remaining references (GUPs, IOMMU Mappings etc...) Userspace calls vcpu_run(): KVM (gmem) sees that there aren't any references, sets state to PRIVATE
Case 2 (alternative 1):
Userspace doesn't release its references
Userspace calls vcpu_run(): KVM (gmem) sees that there are still references, exits back to userspace with KVM_EXIT_UNSHARE
Case 2 (alternative 2):
Userspace doesn't release its references
Userspace calls vcpu_run(): KVM (gmem) sees that there are still references, unmaps folio from guest, but allows it to run (until it tries to fault in the folio)
Guest tries to fault in folio that still has reference, KVM does not allow that (it sees that the folio is shared, and it doesn't fault in shared folios to confidential guests)
KVM exits back to userspace with KVM_EXIT_UNSHARE
As I mentioned, the alternatives above are _not_ set in core KVM code. They can vary by architecture of VM type, depending on the policy, support, etc..
Now for your example please on how this would work with IOCTLs :)
Thanks,
/fuad
Quoted 20 lines
Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 100 lines · Fuad Tabba
Hi Vishal,
Quoted 96 lines · Vishal Annapurve
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?I am referring to guest fault handling in KVM.
Quoted 23 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.
Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Who is *we* here? Which entity will retry conversion?
Userspace will re-attempt the VCPU run.
Then KVM will have to keep track of the ranges that need conversion across exits. I think it's cleaner to let userspace make the decision and invoke conversion without carrying additional state in KVM about guest request.
Quoted 26 lines
Quoted 11 lines
Quoted 10 lines
Quoted 5 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Quoted 5 lines
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the
guest_memfdpages for the range being converted.Yes. Userspace will know that it needs to do that on the VCPU exit, which informs it of the guest's hypervisor request to unshare (convert from shared to private) the page.
Quoted 4 lines
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
Quoted 4 lines
How can the guest erroneously try to take away memory? If the guest sends a hypervisor request asking for a conversion of memory that doesn't belong to it, then I would expect the hypervisor to prevent that.
Making a range as private is effectively disallowing host from accessing those ranges -> so taking away memory.
Quoted 27 lines
I don't see how having an IOCTL to trigger the conversion is needed to allow conversion failure. How is that different from userspace ignoring or delaying releasing all references it has for the conversion request?
Quoted 9 lines
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for
guest_memfdpages? Can you help walk an example sequence for shared to private conversion specifically aroundguest_memfdoffset states?To make sure that we are discussing the same scenario: can you do the same as well please --- walk me through an example sequence for shared to private conversion specifically around
guest_memfdoffset states With the IOCTLs involved?Here is an example that I have implemented and tested with pKVM. Note that there are alternatives, the flow below is architecture or even vm-type dependent. None of this code is code KVM code and the behaviour could vary.
Quoted 5 lines
Assuming the folio is shared with the host:
Guest sends unshare hypercall to the hypervisor
Hypervisor forwards request to KVM (gmem) (having done due diligence) KVM (gmem) performs anunmap_folio(), exits to userspace with
For x86 CoCo VM usecases I was talking about, userspace would like to avoid unmap_mapping_range() on the range before it's safe to unshare the range.
Quoted 52 lines
KVM_EXIT_UNSHAREand all the information about the folio being unsharedCase 1:
Userspace removes any remaining references (GUPs, IOMMU Mappings etc...) Userspace callsvcpu_run(): KVM (gmem) sees that there aren't any references, sets state to PRIVATECase 2 (alternative 1):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, exits back to userspace withKVM_EXIT_UNSHARECase 2 (alternative 2):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, unmaps folio from guest, but allows it to run (until it tries to fault in the folio)
Guest tries to fault in folio that still has reference, KVM does not allow that (it sees that the folio is shared, and it doesn't fault in shared folios to confidential guests)
KVM exits back to userspace withKVM_EXIT_UNSHAREAs I mentioned, the alternatives above are _not_ set in core KVM code. They can vary by architecture of VM type, depending on the policy, support, etc..
Now for your example please on how this would work with IOCTLs :)
Thanks,
/fuadQuoted 20 lines
Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Vishal,
Quoted 105 lines · Vishal Annapurve
Quoted 103 lines · Fuad Tabba
Hi Vishal,
Quoted 99 lines · Vishal Annapurve
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?I am referring to guest fault handling in KVM.
Quoted 25 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Who is *we* here? Which entity will retry conversion?
Userspace will re-attempt the VCPU run.
Then KVM will have to keep track of the ranges that need conversion across exits. I think it's cleaner to let userspace make the decision and invoke conversion without carrying additional state in KVM about guest request.
I disagree. I think it's cleaner not to introduce a user interface, and just to track the reason for the last exit, along with the required additional data. KVM is responsible already for handling the workflow, why delegate this last part to the VMM?
Quoted 26 lines
Quoted 26 lines
Quoted 11 lines
Quoted 10 lines
Quoted 5 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Quoted 5 lines
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the
guest_memfdpages for the range being converted.Yes. Userspace will know that it needs to do that on the VCPU exit, which informs it of the guest's hypervisor request to unshare (convert from shared to private) the page.
Quoted 4 lines
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
Quoted 7 lines
Quoted 4 lines
How can the guest erroneously try to take away memory? If the guest sends a hypervisor request asking for a conversion of memory that doesn't belong to it, then I would expect the hypervisor to prevent that.
Making a range as private is effectively disallowing host from accessing those ranges -> so taking away memory.
You said "erroneously" earlier. My question is, how can the guest *erroneously* try to take away memory? This is the normal flow of guest/host relations. The memory is the guest's: it decides when to share it with the host, and it can take it away.
Quoted 29 lines
Quoted 29 lines
I don't see how having an IOCTL to trigger the conversion is needed to allow conversion failure. How is that different from userspace ignoring or delaying releasing all references it has for the conversion request?
Quoted 9 lines
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for
guest_memfdpages? Can you help walk an example sequence for shared to private conversion specifically aroundguest_memfdoffset states?To make sure that we are discussing the same scenario: can you do the same as well please --- walk me through an example sequence for shared to private conversion specifically around
guest_memfdoffset states With the IOCTLs involved?Here is an example that I have implemented and tested with pKVM. Note that there are alternatives, the flow below is architecture or even vm-type dependent. None of this code is code KVM code and the behaviour could vary.
Assuming the folio is shared with the host:
Quoted 7 lines
Quoted 3 lines
Guest sends unshare hypercall to the hypervisor
Hypervisor forwards request to KVM (gmem) (having done due diligence) KVM (gmem) performs anunmap_folio(), exits to userspace withFor x86 CoCo VM usecases I was talking about, userspace would like to avoid
unmap_mapping_range()on the range before it's safe to unshare the range.
Why? There is no harm in userspace unmapping before the memory isn't shared. I don't see the problem with that.
You still haven't responded to my question from the previous email:
can you please return the favor and walk me through an example sequence for shared to private conversion specifically around guest_memfd offset states with the IOCTLs involved? :D
Thanks!
/fuad
Quoted 52 lines
Quoted 52 lines
KVM_EXIT_UNSHAREand all the information about the folio being unsharedCase 1:
Userspace removes any remaining references (GUPs, IOMMU Mappings etc...) Userspace callsvcpu_run(): KVM (gmem) sees that there aren't any references, sets state to PRIVATECase 2 (alternative 1):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, exits back to userspace withKVM_EXIT_UNSHARECase 2 (alternative 2):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, unmaps folio from guest, but allows it to run (until it tries to fault in the folio)
Guest tries to fault in folio that still has reference, KVM does not allow that (it sees that the folio is shared, and it doesn't fault in shared folios to confidential guests)
KVM exits back to userspace withKVM_EXIT_UNSHAREAs I mentioned, the alternatives above are _not_ set in core KVM code. They can vary by architecture of VM type, depending on the policy, support, etc..
Now for your example please on how this would work with IOCTLs :)
Thanks,
/fuadQuoted 20 lines
Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Fuad Tabba <[email protected]> writes:
Let me try to bridge the gap here beginning with the flow we were counting on for a shared to private conversion, for TDX:
1. Guest sends unshare hypercall to the hypervisor
2. (For x86 IIUC hypervisor is the same as KVM) KVM forwards the request to userspace via a KVM_EXIT_HYPERCALL, with KVM_HC_MAP_GPA_RANGE as the hypercall number.
KVM also records that the guest wanted a shared to private conversion, the gpa and size of the request (no change from now, KVM already records that information in struct kvm_run) [1]
3. Userspace will do necessary coordination in userspace, then call the conversion ioctl, passing the parameters along to the ioctl.
4. Ioctl goes to guest_memfd, guest_memfd unmaps the pages, checks refcounts. If there's anything unexpected, error out to userspace. If all is well, flip shareability, exit to userspace with success.
5. Userspace calls vcpu_run() again, the handler for KVM_HC_MAP_GPA_RANGE will tell the guest that userspace was able to fulfill guest request with hypercall.ret set to 0 and then the guest will continue.
6. On the next fault guest_memfd will allow the private fault from the guest.
The flow you're proposing works too, with some changes, but it's probably okay for x86 to have a slightly different flow anyway: (I refactored the steps you outlined)
Quoted 1 line
1. Guest sends unshare hypercall to the hypervisor
Same
Quoted 1 line
2. Hypervisor forwards request to KVM (gmem) (having done due diligence)
For x86 IIUC hypervisor is the same as KVM, so there's no forwarding to KVM.
Quoted 2 lines
3. KVM (gmem) performs an
unmap_folio(), exits to userspace withKVM_EXIT_UNSHAREand all the information about the folio being unshared
The KVM_EXIT_UNSHARE here would correspond to x86's KVM_HC_MAP_GPA_RANGE.
Unmapping before exiting with KVM_EXIT_UNSHARE here might be a little premature since userspace may have to do some stuff before permitting the conversion. For example, the memory may be mapped into another userspace driver process, which needs to first be stopped.
But no issue though, as long as we don't flip shareability, if the host uses the memory, the kvm_gmem_fault_shared() will just happen again, nullifying the unmapping.
We could just shift the unmapping till after vcpu_run() is called again.
Quoted 2 lines
4. Userspace will do necessary coordination in userspace, then do
vcpu_run()
There's another layer here, at least for x86, as to whether the coordination was successful. For x86's KVM_HC_MAP_GPA_RANGE, userspace can indicate a non-zero hypercall.ret for error.
For unsuccessful coordinations, userspace sets hypercall.ret to error and the vcpu_run() handler doesn't try the conversion. Guest is informed of hypercall error and guest will figure it out.
Quoted 2 lines
5. Successful coordination, case 1:
vcpu_run()knows the last exit wasKVM_EXIT_UNSHAREand will set state to PRIVATE
For case 1, userspace will set hypercall.ret == 0, guest_memfd will do the conversion, basically calling the same function that the ioctl calls within guest_memfd.
Quoted 2 lines
5. Successful coordination, case 2, alternative 1:
vcpu_run()knows the last exit wasKVM_EXIT_UNSHARE
Exit to userspace with KVM_EXIT_MEMORY_FAULT.
Quoted 2 lines
5. Successful coordination, case 2, alternative 2:
vcpu_run()knows the last exit wasKVM_EXIT_UNSHARE
Forward hypercall.ret == 0 to the guest. Since the conversion was not performed, the next fault will be mismatched and there will be a KVM_EXIT_MEMORY_FAULT.
Quoted 117 lines
Hi Vishal,
Quoted 105 lines · Vishal Annapurve
Quoted 103 lines · Fuad Tabba
Hi Vishal,
Quoted 99 lines · Vishal Annapurve
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?I am referring to guest fault handling in KVM.
Quoted 25 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Who is *we* here? Which entity will retry conversion?
Userspace will re-attempt the VCPU run.
Then KVM will have to keep track of the ranges that need conversion across exits. I think it's cleaner to let userspace make the decision and invoke conversion without carrying additional state in KVM about guest request.
Quoted 5 lines
I disagree. I think it's cleaner not to introduce a user interface, and just to track the reason for the last exit, along with the required additional data. KVM is responsible already for handling the workflow, why delegate this last part to the VMM?
I believe Fuad's concern is the complexity of adding and maintaining another ioctl, as opposed to having vcpu_run() do the conversions.
I think the two options are basically the same in that both are actually adding some form of user contract, just in different places.
For the ioctl approach, in this RFCv2 I added a error_offset field so that userspace has a hint of where the conversion had an issue. the ioctl also returns errors to indicate what went wrong, like -EINVAL or -ENOMEM if perhaps splitting the page required memory and there wasn't any, or the kernel ran out of memory trying to update mappability.
If we want to provide the same level of error information for the vcpu_run() approach, we should probably add error_offset to KVM_EXIT_MEMORY_FAULT so that on a conversion failure we could re-exit to userspace with more information about the error_offset.
So what we're really comparing is two ways to perform the conversion (1) via a direct ioctl and (2) via vcpu_run().
I think having a direct ioctl is cleaner because it doesn't involve vCPUs for a memory operation.
Conceptually, the conversion is a memory operation belonging to memory in the guest_memfd. Hence, the conversion operation is better addressed directly to the memory via a direct ioctl.
For this same reason, we didn't want to do the conversion via the KVM_SET_MEMORY_ATTRIBUTES ioctl. KVM_SET_MEMORY_ATTRIBUTES is an operation for KVM's view of guest_memfd, which is linked to but not directly the same as a memory operation.
By having a direct ioctl over using KVM_SET_MEMORY_ATTRIBUTES, we avoid having a dependency where memslots must first be bound to guest_memfd for the conversion to work.
When rebooting, the memslots may not yet be bound to the guest_memfd, but we want to reset the guest_memfd's to private. If we use KVM_SET_MEMORY_ATTRIBUTES to convert, we'd be forced to first bind, then convert. If we had a direct ioctl, we don't have this restriction.
If we do the conversion via vcpu_run() we would be forced to handle conversions only with a vcpu_run() and only the guest can initiate a conversion.
On a guest boot for TDX, the memory is assumed to be private. If the we gave it memory set as shared, we'd just have a bunch of KVM_EXIT_MEMORY_FAULTs that slow down boot. Hence on a guest reboot, we will want to reset the guest memory to private.
We could say the firmware should reset memory to private on guest reboot, but we can't force all guests to update firmware.
Quoted 31 lines
Quoted 31 lines
Quoted 30 lines
Quoted 11 lines
Quoted 10 lines
Quoted 5 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Quoted 5 lines
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the
guest_memfdpages for the range being converted.Yes. Userspace will know that it needs to do that on the VCPU exit, which informs it of the guest's hypervisor request to unshare (convert from shared to private) the page.
Quoted 4 lines
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
How can the guest erroneously try to take away memory? If the guest sends a hypervisor request asking for a conversion of memory that doesn't belong to it, then I would expect the hypervisor to prevent that.
Quoted 8 lines
Quoted 2 lines
Making a range as private is effectively disallowing host from accessing those ranges -> so taking away memory.
You said "erroneously" earlier. My question is, how can the guest *erroneously* try to take away memory? This is the normal flow of guest/host relations. The memory is the guest's: it decides when to share it with the host, and it can take it away.
See above, it's not really erroneous as long as we kvm_gmem_fault_shared() can still happen, since after unmapping, any host access will just fault the page again.
Quoted 33 lines
Quoted 33 lines
Quoted 32 lines
I don't see how having an IOCTL to trigger the conversion is needed to allow conversion failure. How is that different from userspace ignoring or delaying releasing all references it has for the conversion request?
Quoted 9 lines
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for
guest_memfdpages? Can you help walk an example sequence for shared to private conversion specifically aroundguest_memfdoffset states?To make sure that we are discussing the same scenario: can you do the same as well please --- walk me through an example sequence for shared to private conversion specifically around
guest_memfdoffset states With the IOCTLs involved?Here is an example that I have implemented and tested with pKVM. Note that there are alternatives, the flow below is architecture or even vm-type dependent. None of this code is code KVM code and the behaviour could vary.
Assuming the folio is shared with the host:
Guest sends unshare hypercall to the hypervisor
Hypervisor forwards request to KVM (gmem) (having done due diligence) KVM (gmem) performs anunmap_folio(), exits to userspace with
Quoted 7 lines
Quoted 3 lines
For x86 CoCo VM usecases I was talking about, userspace would like to avoid
unmap_mapping_range()on the range before it's safe to unshare the range.Why? There is no harm in userspace unmapping before the memory isn't shared. I don't see the problem with that.
Yes, no harm done, just possible remapping after unmapping.
Quoted 5 lines
You still haven't responded to my question from the previous email:
can you please return the favor and walk me through an example sequence for shared to private conversion specifically aroundguest_memfdoffset states with the IOCTLs involved? :D
Right at the top :)
Quoted 56 lines
Thanks!
/fuadQuoted 52 lines
Quoted 52 lines
KVM_EXIT_UNSHAREand all the information about the folio being unsharedCase 1:
Userspace removes any remaining references (GUPs, IOMMU Mappings etc...) Userspace callsvcpu_run(): KVM (gmem) sees that there aren't any references, sets state to PRIVATECase 2 (alternative 1):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, exits back to userspace withKVM_EXIT_UNSHARECase 2 (alternative 2):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, unmaps folio from guest, but allows it to run (until it tries to fault in the folio)
Guest tries to fault in folio that still has reference, KVM does not allow that (it sees that the folio is shared, and it doesn't fault in shared folios to confidential guests)
KVM exits back to userspace withKVM_EXIT_UNSHAREAs I mentioned, the alternatives above are _not_ set in core KVM code. They can vary by architecture of VM type, depending on the policy, support, etc..
Now for your example please on how this would work with IOCTLs :)
Thanks,
/fuadQuoted 20 lines
Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Ackerley,
Quoted 83 lines · Ackerley Tng
Fuad Tabba <[email protected]> writes:
Let me try to bridge the gap here beginning with the flow we were counting on for a shared to private conversion, for TDX:
1. Guest sends unshare hypercall to the hypervisor
2. (For x86 IIUC hypervisor is the same as KVM) KVM forwards the request to userspace via a
KVM_EXIT_HYPERCALL, withKVM_HC_MAP_GPA_RANGEas the hypercall number.KVM also records that the guest wanted a shared to private conversion, the gpa and size of the request (no change from now, KVM already records that information in struct
kvm_run) [1]3. Userspace will do necessary coordination in userspace, then call the conversion ioctl, passing the parameters along to the ioctl.
4. Ioctl goes to
guest_memfd,guest_memfdunmaps the pages, checks refcounts. If there's anything unexpected, error out to userspace. If all is well, flip shareability, exit to userspace with success.5. Userspace calls
vcpu_run()again, the handler forKVM_HC_MAP_GPA_RANGEwill tell the guest that userspace was able to fulfill guest request with hypercall.ret set to 0 and then the guest will continue.6. On the next fault
guest_memfdwill allow the private fault from the guest.The flow you're proposing works too, with some changes, but it's probably okay for x86 to have a slightly different flow anyway: (I refactored the steps you outlined)
Quoted 1 line
1. Guest sends unshare hypercall to the hypervisor
Same
Quoted 1 line
2. Hypervisor forwards request to KVM (gmem) (having done due diligence)
For x86 IIUC hypervisor is the same as KVM, so there's no forwarding to KVM.
Quoted 2 lines
3. KVM (gmem) performs an
unmap_folio(), exits to userspace withKVM_EXIT_UNSHAREand all the information about the folio being unsharedThe
KVM_EXIT_UNSHAREhere would correspond to x86'sKVM_HC_MAP_GPA_RANGE.Unmapping before exiting with
KVM_EXIT_UNSHAREhere might be a little premature since userspace may have to do some stuff before permitting the conversion. For example, the memory may be mapped into another userspace driver process, which needs to first be stopped.But no issue though, as long as we don't flip shareability, if the host uses the memory, the
kvm_gmem_fault_shared()will just happen again, nullifying the unmapping.We could just shift the unmapping till after
vcpu_run()is called again.Quoted 2 lines
4. Userspace will do necessary coordination in userspace, then do
vcpu_run()There's another layer here, at least for x86, as to whether the coordination was successful. For x86's
KVM_HC_MAP_GPA_RANGE, userspace can indicate a non-zero hypercall.ret for error.For unsuccessful coordinations, userspace sets hypercall.ret to error and the
vcpu_run()handler doesn't try the conversion. Guest is informed of hypercall error and guest will figure it out.Quoted 2 lines
5. Successful coordination, case 1:
vcpu_run()knows the last exit wasKVM_EXIT_UNSHAREand will set state to PRIVATEFor case 1, userspace will set hypercall.ret == 0,
guest_memfdwill do the conversion, basically calling the same function that the ioctl calls withinguest_memfd.Quoted 2 lines
5. Successful coordination, case 2, alternative 1:
vcpu_run()knows the last exit wasKVM_EXIT_UNSHARE
Quoted 8 lines
Exit to userspace with
KVM_EXIT_MEMORY_FAULT.Quoted 2 lines
5. Successful coordination, case 2, alternative 2:
vcpu_run()knows the last exit wasKVM_EXIT_UNSHAREForward hypercall.ret == 0 to the guest. Since the conversion was not performed, the next fault will be mismatched and there will be a
KVM_EXIT_MEMORY_FAULT.
So far so good. With regard to the flow, in the code that I had, all the specific details were arm64, and even pKVM specific. None of it was baked into core KVM code, since of course, different architectures, and even different VM types, will vary significantly. Arm CCA for example is closer to TDX than it is to pKVM. Moreover, it was just a hack at getting something reasonable that works, as a proof of concept.
This is one of the reasons I'm not a fan of having a userspace IOCTL as an additional required step as part of this protocol. KVM exits already exist (*), and we need them anyway here. The flow above is VM-type specific, and since much of it isn't exposed to the user: it's easy (and likely) to change. Having an IOCTL and adding another step in the process makes it more difficult to change things later.
(*) Try saying that ten times fast! Note: first word is exit, second word is exist :)
Quoted 135 lines
Quoted 117 lines
Hi Vishal,
Quoted 105 lines · Vishal Annapurve
Quoted 103 lines · Fuad Tabba
Hi Vishal,
Quoted 99 lines · Vishal Annapurve
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?I am referring to guest fault handling in KVM.
Quoted 25 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Who is *we* here? Which entity will retry conversion?
Userspace will re-attempt the VCPU run.
Then KVM will have to keep track of the ranges that need conversion across exits. I think it's cleaner to let userspace make the decision and invoke conversion without carrying additional state in KVM about guest request.
Quoted 5 lines
I disagree. I think it's cleaner not to introduce a user interface, and just to track the reason for the last exit, along with the required additional data. KVM is responsible already for handling the workflow, why delegate this last part to the VMM?
I believe Fuad's concern is the complexity of adding and maintaining another ioctl, as opposed to having
vcpu_run()do the conversions.I think the two options are basically the same in that both are actually adding some form of user contract, just in different places.
For the ioctl approach, in this RFCv2 I added a
error_offsetfield so that userspace has a hint of where the conversion had an issue. the ioctl also returns errors to indicate what went wrong, like-EINVALor-ENOMEMif perhaps splitting the page required memory and there wasn't any, or the kernel ran out of memory trying to update mappability.
Quoted 8 lines
If we want to provide the same level of error information for the
vcpu_run()approach, we should probably adderror_offsettoKVM_EXIT_MEMORY_FAULTso that on a conversion failure we could re-exit to userspace with more information about theerror_offset.So what we're really comparing is two ways to perform the conversion (1) via a direct ioctl and (2) via
vcpu_run().
That's exactly right.
Quoted 25 lines
I think having a direct ioctl is cleaner because it doesn't involve vCPUs for a memory operation.
Conceptually, the conversion is a memory operation belonging to memory in the
guest_memfd. Hence, the conversion operation is better addressed directly to the memory via a direct ioctl.For this same reason, we didn't want to do the conversion via the
KVM_SET_MEMORY_ATTRIBUTESioctl.KVM_SET_MEMORY_ATTRIBUTESis an operation for KVM's view ofguest_memfd, which is linked to but not directly the same as a memory operation.By having a direct ioctl over using
KVM_SET_MEMORY_ATTRIBUTES, we avoid having a dependency where memslots must first be bound toguest_memfdfor the conversion to work.When rebooting, the memslots may not yet be bound to the
guest_memfd, but we want to reset the guest_memfd's to private. If we useKVM_SET_MEMORY_ATTRIBUTESto convert, we'd be forced to first bind, then convert. If we had a direct ioctl, we don't have this restriction.If we do the conversion via
vcpu_run()we would be forced to handle conversions only with avcpu_run()and only the guest can initiate a conversion.
Quoted 7 lines
On a guest boot for TDX, the memory is assumed to be private. If the we gave it memory set as shared, we'd just have a bunch of
KVM_EXIT_MEMORY_FAULTsthat slow down boot. Hence on a guest reboot, we will want to reset the guest memory to private.We could say the firmware should reset memory to private on guest reboot, but we can't force all guests to update firmware.
Here is where I disagree. I do think that this is the CoCo guest's responsibility (and by guest I include its firmware) to fix its own state after a reboot. How would the host even know that a guest is rebooting if it's a CoCo guest?
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help. Or somehow the host does know that, i.e., via a hypercall that indicates that. In which case, we could have it so that for that type of VM, we would reconvert its pages to private on a reboot.
Additionally, we could introduce range operations for sharing/unsharing, to avoid having to have an exit for every one.
Quoted 40 lines
Quoted 31 lines
Quoted 31 lines
Quoted 30 lines
Quoted 11 lines
Quoted 10 lines
Quoted 5 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Quoted 5 lines
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the
guest_memfdpages for the range being converted.Yes. Userspace will know that it needs to do that on the VCPU exit, which informs it of the guest's hypervisor request to unshare (convert from shared to private) the page.
Quoted 4 lines
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
How can the guest erroneously try to take away memory? If the guest sends a hypervisor request asking for a conversion of memory that doesn't belong to it, then I would expect the hypervisor to prevent that.
Quoted 8 lines
Quoted 2 lines
Making a range as private is effectively disallowing host from accessing those ranges -> so taking away memory.
You said "erroneously" earlier. My question is, how can the guest *erroneously* try to take away memory? This is the normal flow of guest/host relations. The memory is the guest's: it decides when to share it with the host, and it can take it away.
Quoted 3 lines
See above, it's not really erroneous as long as we
kvm_gmem_fault_shared()can still happen, since after unmapping, any host access will just fault the page again.
I was confused by the word "erroneous", as you would expect that for a CoCo guest, the host wouldn't (or shouldn't) know the intention behind a CoCo guest's access. I would expect that erroneous guest accesses would be handled by the hypervisor. But I think we're on the same page now.
Quoted 43 lines
Quoted 33 lines
Quoted 33 lines
Quoted 32 lines
I don't see how having an IOCTL to trigger the conversion is needed to allow conversion failure. How is that different from userspace ignoring or delaying releasing all references it has for the conversion request?
Quoted 9 lines
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for
guest_memfdpages? Can you help walk an example sequence for shared to private conversion specifically aroundguest_memfdoffset states?To make sure that we are discussing the same scenario: can you do the same as well please --- walk me through an example sequence for shared to private conversion specifically around
guest_memfdoffset states With the IOCTLs involved?Here is an example that I have implemented and tested with pKVM. Note that there are alternatives, the flow below is architecture or even vm-type dependent. None of this code is code KVM code and the behaviour could vary.
Assuming the folio is shared with the host:
Guest sends unshare hypercall to the hypervisor
Hypervisor forwards request to KVM (gmem) (having done due diligence) KVM (gmem) performs anunmap_folio(), exits to userspace withQuoted 7 lines
Quoted 3 lines
For x86 CoCo VM usecases I was talking about, userspace would like to avoid
unmap_mapping_range()on the range before it's safe to unshare the range.Why? There is no harm in userspace unmapping before the memory isn't shared. I don't see the problem with that.
Yes, no harm done, just possible remapping after unmapping.
Quoted 7 lines
Quoted 5 lines
You still haven't responded to my question from the previous email:
can you please return the favor and walk me through an example sequence for shared to private conversion specifically aroundguest_memfdoffset states with the IOCTLs involved? :DRight at the top :)
Thank you Ackerley!
Cheers,
/fuad
Quoted 57 lines
Quoted 57 lines
Thanks!
/fuadQuoted 52 lines
Quoted 52 lines
KVM_EXIT_UNSHAREand all the information about the folio being unsharedCase 1:
Userspace removes any remaining references (GUPs, IOMMU Mappings etc...) Userspace callsvcpu_run(): KVM (gmem) sees that there aren't any references, sets state to PRIVATECase 2 (alternative 1):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, exits back to userspace withKVM_EXIT_UNSHARECase 2 (alternative 2):
Userspace doesn't release its references
Userspace callsvcpu_run(): KVM (gmem) sees that there are still references, unmaps folio from guest, but allows it to run (until it tries to fault in the folio)
Guest tries to fault in folio that still has reference, KVM does not allow that (it sees that the folio is shared, and it doesn't fault in shared folios to confidential guests)
KVM exits back to userspace withKVM_EXIT_UNSHAREAs I mentioned, the alternatives above are _not_ set in core KVM code. They can vary by architecture of VM type, depending on the policy, support, etc..
Now for your example please on how this would work with IOCTLs :)
Thanks,
/fuadQuoted 20 lines
Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 15 lines · Fuad Tabba
....
Quoted 14 lines
When rebooting, the memslots may not yet be bound to the
guest_memfd, but we want to reset the guest_memfd's to private. If we useKVM_SET_MEMORY_ATTRIBUTESto convert, we'd be forced to first bind, then convert. If we had a direct ioctl, we don't have this restriction.If we do the conversion via
vcpu_run()we would be forced to handle conversions only with avcpu_run()and only the guest can initiate a conversion.On a guest boot for TDX, the memory is assumed to be private. If the we gave it memory set as shared, we'd just have a bunch of
KVM_EXIT_MEMORY_FAULTsthat slow down boot. Hence on a guest reboot, we will want to reset the guest memory to private.
Quoted 7 lines
Quoted 2 lines
We could say the firmware should reset memory to private on guest reboot, but we can't force all guests to update firmware.
Here is where I disagree. I do think that this is the CoCo guest's responsibility (and by guest I include its firmware) to fix its own state after a reboot. How would the host even know that a guest is rebooting if it's a CoCo guest?
There are a bunch of complexities here, reboot sequence on x86 can be triggered using multiple ways that I don't fully understand, but few of them include reading/writing to "reset register" in MMIO/PCI config space that are emulated by the host userspace directly. Host has to know when the guest is shutting down to manage it's lifecycle.
x86 CoCo VM firmwares don't support warm/soft reboot and even if it does in future, guest kernel can choose a different reboot mechanism. So guest reboot needs to be emulated by always starting from scratch. This sequence needs initial guest firmware payload to be installed into private ranges of guest_memfd.
Quoted 3 lines
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help.
Host does know that the guest is rebooting.
Quoted 3 lines
Or somehow the host does know that, i.e., via a hypercall that indicates that. In which case, we could have it so that for that type of VM, we would reconvert its pages to private on a reboot.
This possibly could be solved by resetting the ranges to private when binding with a memslot of certain VM type. But then Google also has a usecase to support intrahost migration where a live VM and associated guest_memfd files are bound to new KVM VM and memslots.
Otherwise, we need an additional contract between userspace/KVM to intercept/handle guest_memfd range reset.
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Vishal,
Quoted 25 lines · Vishal Annapurve
Quoted 15 lines · Fuad Tabba
....
Quoted 14 lines
When rebooting, the memslots may not yet be bound to the
guest_memfd, but we want to reset the guest_memfd's to private. If we useKVM_SET_MEMORY_ATTRIBUTESto convert, we'd be forced to first bind, then convert. If we had a direct ioctl, we don't have this restriction.If we do the conversion via
vcpu_run()we would be forced to handle conversions only with avcpu_run()and only the guest can initiate a conversion.On a guest boot for TDX, the memory is assumed to be private. If the we gave it memory set as shared, we'd just have a bunch of
KVM_EXIT_MEMORY_FAULTsthat slow down boot. Hence on a guest reboot, we will want to reset the guest memory to private.Quoted 7 lines
Quoted 2 lines
We could say the firmware should reset memory to private on guest reboot, but we can't force all guests to update firmware.
Here is where I disagree. I do think that this is the CoCo guest's responsibility (and by guest I include its firmware) to fix its own state after a reboot. How would the host even know that a guest is rebooting if it's a CoCo guest?
Quoted 5 lines
There are a bunch of complexities here, reboot sequence on x86 can be triggered using multiple ways that I don't fully understand, but few of them include reading/writing to "reset register" in MMIO/PCI config space that are emulated by the host userspace directly. Host has to know when the guest is shutting down to manage it's lifecycle.
In that case, I think we need to fully understand these complexities before adding new IOCTLs. It could be that once we understand these issues, we find that we don't need these IOCTLs. It's hard to justify adding an IOCTL for something we don't understand.
Quoted 11 lines
x86 CoCo VM firmwares don't support warm/soft reboot and even if it does in future, guest kernel can choose a different reboot mechanism. So guest reboot needs to be emulated by always starting from scratch. This sequence needs initial guest firmware payload to be installed into private ranges of
guest_memfd.Quoted 3 lines
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help.
Host does know that the guest is rebooting.
In that case, that (i.e., the host finding out that the guest is rebooting) could trigger the conversion back to private. No need for an IOCTL.
Quoted 11 lines
Quoted 3 lines
Or somehow the host does know that, i.e., via a hypercall that indicates that. In which case, we could have it so that for that type of VM, we would reconvert its pages to private on a reboot.
This possibly could be solved by resetting the ranges to private when binding with a memslot of certain VM type. But then Google also has a usecase to support intrahost migration where a live VM and associated
guest_memfdfiles are bound to new KVM VM and memslots.Otherwise, we need an additional contract between userspace/KVM to intercept/handle
guest_memfdrange reset.
Then this becomes a migration issue to be solved then, not a huge page support issue. If such IOCTLs are needed for migration, it's too early to add them now.
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 35 lines · Fuad Tabba
Hi Vishal,
Quoted 25 lines · Vishal Annapurve
Quoted 15 lines · Fuad Tabba
....
Quoted 14 lines
When rebooting, the memslots may not yet be bound to the
guest_memfd, but we want to reset the guest_memfd's to private. If we useKVM_SET_MEMORY_ATTRIBUTESto convert, we'd be forced to first bind, then convert. If we had a direct ioctl, we don't have this restriction.If we do the conversion via
vcpu_run()we would be forced to handle conversions only with avcpu_run()and only the guest can initiate a conversion.On a guest boot for TDX, the memory is assumed to be private. If the we gave it memory set as shared, we'd just have a bunch of
KVM_EXIT_MEMORY_FAULTsthat slow down boot. Hence on a guest reboot, we will want to reset the guest memory to private.Quoted 7 lines
Quoted 2 lines
We could say the firmware should reset memory to private on guest reboot, but we can't force all guests to update firmware.
Here is where I disagree. I do think that this is the CoCo guest's responsibility (and by guest I include its firmware) to fix its own state after a reboot. How would the host even know that a guest is rebooting if it's a CoCo guest?
Quoted 5 lines
There are a bunch of complexities here, reboot sequence on x86 can be triggered using multiple ways that I don't fully understand, but few of them include reading/writing to "reset register" in MMIO/PCI config space that are emulated by the host userspace directly. Host has to know when the guest is shutting down to manage it's lifecycle.
Quoted 5 lines
In that case, I think we need to fully understand these complexities before adding new IOCTLs. It could be that once we understand these issues, we find that we don't need these IOCTLs. It's hard to justify adding an IOCTL for something we don't understand.
I don't understand all the ways x86 guest can trigger reboot but I do know that x86 CoCo linux guest kernel triggers reset using MMIO/PCI config register write that is emulated by host userspace.
Quoted 7 lines
Quoted 7 lines
x86 CoCo VM firmwares don't support warm/soft reboot and even if it does in future, guest kernel can choose a different reboot mechanism. So guest reboot needs to be emulated by always starting from scratch. This sequence needs initial guest firmware payload to be installed into private ranges of
guest_memfd.Quoted 1 line
Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help.
Host does know that the guest is rebooting.
In that case, that (i.e., the host finding out that the guest is rebooting) could trigger the conversion back to private. No need for an IOCTL.
In the reboot scenarios, it's the host userspace finding out that the guest kernel wants to reboot.
Quoted 10 lines
Quoted 10 lines
Quoted 4 lines
Or somehow the host does know that, i.e., via a hypercall that indicates that. In which case, we could have it so that for that type of VM, we would reconvert its pages to private on a reboot.
This possibly could be solved by resetting the ranges to private when binding with a memslot of certain VM type. But then Google also has a usecase to support intrahost migration where a live VM and associated
guest_memfdfiles are bound to new KVM VM and memslots.
Quoted 6 lines
Quoted 2 lines
Otherwise, we need an additional contract between userspace/KVM to intercept/handle
guest_memfdrange reset.Then this becomes a migration issue to be solved then, not a huge page support issue. If such IOCTLs are needed for migration, it's too early to add them now.
The guest_memfd ioctl is not needed for migration but to change/reset guest_memfd range attributes. I am saying that migration usecase can conflict with some ways that we can solve resetting guest_memfd range attributes without adding a new IOCTL as migration closely resembles reboot scenario as both of them can/need reusing the same guest memory files but one needs to preserve guest memory state.
Reiterating my understanding here, guest memfd ioctl can be used by host userspace to -
1) Change guest memfd range attributes during memory conversion
- This can be handled by KVM hypercall exits in theory as you are
suggesting but Ackerley and me are still thinking that this is a memory operation that goes beyond vcpu scope and will involve interaction with IOMMU backend as well, it's cleaner to have a separate guest memfd specific ioctl for this operation as the impact is even beyond KVM.
2) Reset guest memfd range attributes during guest reboot to allow reusing the same guest memfd files.
- This helps reset the range state to private as needed inline
with initial shared/private configuration chosen at the guest memfd creation.
- This also helps reconstitute all the huge pages back to their
original state that may have gotten split during the runtime of the guest.
This is a host initiated request for guest memfd memory conversion
that we should not be overloading with other KVM interactions in my opinion.
Quoted 3 lines
Cheers,
/fuad
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Vishal,
Quoted 57 lines · Vishal Annapurve
Quoted 35 lines · Fuad Tabba
Hi Vishal,
Quoted 25 lines · Vishal Annapurve
Quoted 15 lines · Fuad Tabba
....
Quoted 14 lines
When rebooting, the memslots may not yet be bound to the
guest_memfd, but we want to reset the guest_memfd's to private. If we useKVM_SET_MEMORY_ATTRIBUTESto convert, we'd be forced to first bind, then convert. If we had a direct ioctl, we don't have this restriction.If we do the conversion via
vcpu_run()we would be forced to handle conversions only with avcpu_run()and only the guest can initiate a conversion.On a guest boot for TDX, the memory is assumed to be private. If the we gave it memory set as shared, we'd just have a bunch of
KVM_EXIT_MEMORY_FAULTsthat slow down boot. Hence on a guest reboot, we will want to reset the guest memory to private.Quoted 7 lines
Quoted 2 lines
We could say the firmware should reset memory to private on guest reboot, but we can't force all guests to update firmware.
Here is where I disagree. I do think that this is the CoCo guest's responsibility (and by guest I include its firmware) to fix its own state after a reboot. How would the host even know that a guest is rebooting if it's a CoCo guest?
Quoted 5 lines
There are a bunch of complexities here, reboot sequence on x86 can be triggered using multiple ways that I don't fully understand, but few of them include reading/writing to "reset register" in MMIO/PCI config space that are emulated by the host userspace directly. Host has to know when the guest is shutting down to manage it's lifecycle.
Quoted 5 lines
In that case, I think we need to fully understand these complexities before adding new IOCTLs. It could be that once we understand these issues, we find that we don't need these IOCTLs. It's hard to justify adding an IOCTL for something we don't understand.
I don't understand all the ways x86 guest can trigger reboot but I do know that x86 CoCo linux guest kernel triggers reset using MMIO/PCI config register write that is emulated by host userspace.
Quoted 10 lines
Quoted 10 lines
x86 CoCo VM firmwares don't support warm/soft reboot and even if it does in future, guest kernel can choose a different reboot mechanism. So guest reboot needs to be emulated by always starting from scratch. This sequence needs initial guest firmware payload to be installed into private ranges of
guest_memfd.Quoted 3 lines
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help.
Quoted 8 lines
Quoted 5 lines
Quoted 1 line
Host does know that the guest is rebooting.
In that case, that (i.e., the host finding out that the guest is rebooting) could trigger the conversion back to private. No need for an IOCTL.
In the reboot scenarios, it's the host userspace finding out that the guest kernel wants to reboot.
How does the host userspace find that out? If the host userspace is capable of finding that out, then surely KVM is also capable of finding out the same.
Quoted 24 lines
Quoted 10 lines
Quoted 10 lines
Quoted 4 lines
Or somehow the host does know that, i.e., via a hypercall that indicates that. In which case, we could have it so that for that type of VM, we would reconvert its pages to private on a reboot.
This possibly could be solved by resetting the ranges to private when binding with a memslot of certain VM type. But then Google also has a usecase to support intrahost migration where a live VM and associated
guest_memfdfiles are bound to new KVM VM and memslots.Quoted 6 lines
Quoted 2 lines
Otherwise, we need an additional contract between userspace/KVM to intercept/handle
guest_memfdrange reset.Then this becomes a migration issue to be solved then, not a huge page support issue. If such IOCTLs are needed for migration, it's too early to add them now.
The
guest_memfdioctl is not needed for migration but to change/resetguest_memfdrange attributes. I am saying that migration usecase can conflict with some ways that we can solve resettingguest_memfdrange attributes without adding a new IOCTL as migration closely resembles reboot scenario as both of them can/need reusing the same guest memory files but one needs to preserve guest memory state.
Quoted 9 lines
Reiterating my understanding here, guest memfd ioctl can be used by host userspace to -
1) Change guest memfd range attributes during memory conversion
- This can be handled by KVM hypercall exits in theory as you are
suggesting but Ackerley and me are still thinking that this is a memory operation that goes beyond vcpu scope and will involve interaction with IOMMU backend as well, it's cleaner to have a separate guest memfd specific ioctl for this operation as the impact is even beyond KVM.
The IOMMU backend needs to know about the sharing/unsharing, not trigger it. The memory is the guest's. We already have a mechanism for informing userspace of these kinds of events with KVM exits. This doesn't justify adding a new IOCTL.
Quoted 11 lines
2) Reset guest memfd range attributes during guest reboot to allow reusing the same guest memfd files.
- This helps reset the range state to private as needed inline
with initial shared/private configuration chosen at the guest memfd creation.
- This also helps reconstitute all the huge pages back to their
original state that may have gotten split during the runtime of the guest.
This is a host initiated request for guest memfd memory conversion
that we should not be overloading with other KVM interactions in my opinion.
Then, we could argue about whether we need a "reset" IOCTL (not that I am arguing for that). But still, like I said, if the host becomes aware that the confidential guest is rebooting, then surely KVM can be made aware.
I wonder if this might be better suited for the biweekly guest_memfd sync.
Cheers,
/fuad
Quoted 3 lines
Quoted 3 lines
Cheers,
/fuad
Sean Christopherson
maintainerRe: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 36 lines · Fuad Tabba
Quoted 35 lines · Vishal Annapurve
Quoted 8 lines · Fuad Tabba
Quoted 6 lines · Vishal Annapurve
On Wed, May 21, 2025 at 5:36 AM Fuad Tabba <[email protected]> wrote:
There are a bunch of complexities here, reboot sequence on x86 can be triggered using multiple ways that I don't fully understand, but few of them include reading/writing to "reset register" in MMIO/PCI config space that are emulated by the host userspace directly. Host has to know when the guest is shutting down to manage it's lifecycle.Quoted 5 lines
In that case, I think we need to fully understand these complexities before adding new IOCTLs. It could be that once we understand these issues, we find that we don't need these IOCTLs. It's hard to justify adding an IOCTL for something we don't understand.
I don't understand all the ways x86 guest can trigger reboot but I do know that x86 CoCo linux guest kernel triggers reset using MMIO/PCI config register write that is emulated by host userspace.
Quoted 15 lines
Quoted 11 lines
x86 CoCo VM firmwares don't support warm/soft reboot and even if it does in future, guest kernel can choose a different reboot mechanism. So guest reboot needs to be emulated by always starting from scratch. This sequence needs initial guest firmware payload to be installed into private ranges of
guest_memfd.Quoted 3 lines
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help.
Host does know that the guest is rebooting.
In that case, that (i.e., the host finding out that the guest is rebooting) could trigger the conversion back to private. No need for an IOCTL.
Quoted 5 lines
Quoted 2 lines
In the reboot scenarios, it's the host userspace finding out that the guest kernel wants to reboot.
How does the host userspace find that out? If the host userspace is capable of finding that out, then surely KVM is also capable of finding out the same.
Nope, not on x86. Well, not without userspace invoking a new ioctl, which would defeat the purpose of adding these ioctls.
KVM is only responsible for emulating/virtualizing the "CPU". The chipset, e.g. the PCI config space, is fully owned by userspace. KVM doesn't even know whether or not PCI exists for the VM. And reboot may be emulated by simply creating a new KVM instance, i.e. even if KVM was somehow aware of the reboot request, the change in state would happen in an entirely new struct kvm.
That said, Vishal and Ackerley, this patch is a bit lacking on the documentation front. The changelog asserts that:
A guest_memfd ioctl is used because shareability is a property of the memory, and this property should be modifiable independently of the attached struct kvm
but then follows with a very weak and IMO largely irrelevant justification of:
This allows shareability to be modified even if the memory is not yet bound using memslots.
Allowing userspace to change shareability without memslots is one relatively minor flow in one very specific use case.
The real justification for these ioctls is that fundamentally, shareability for in-place conversions is a property of a guest_memfd instance and not a struct kvm instance, and so needs to owned by guest_memfd.
I.e. focus on justifying the change from a design and conceptual perspective, not from a mechanical perspective of a flow that likely's somewhat unique to our specific environment. Y'all are getting deep into the weeds on a random aspect of x86 platform architecture, instead of focusing on the overall design.
The other issue that's likely making this more confusing than it needs to be is that this series is actually two completely different series bundled into one, with very little explanation. Moving shared vs. private ownership into guest_memfd isn't a requirement for 1GiB support, it's a requirement for in-place shared/private conversion in guest_memfd.
For the current guest_memfd implementation, shared vs. private is tracked in the VM via memory attributes, because a guest_memfd instance is *only* private. I.e. shared vs. private is a property of the VM, not of the guest_memfd instance. But when in-place conversion support comes along, ownership of that particular attribute needs to shift to the guest_memfd instance.
I know I gave feedback on earlier posting about there being too series flying around, but shoving two distinct concepts into a single series is not the answer. My complaints about too much noise wasn't that there were multiple series, it was that there was very little coordination and lots of chaos.
If you split this series in two, which should be trivial since you've already organized the patches as a split, then sans the selftests (thank you for those!), in-place conversion support will be its own (much smaller!) series that can focus on that specific aspect of the design, and can provide a cover letter that expounds on the design goals and uAPI.
KVM: guest_memfd: Add CAP KVM_CAP_GMEM_CONVERSION
KVM: Query guest_memfd for private/shared status
KVM: guest_memfd: Skip LRU for guest_memfd folios
KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM: guest_memfd: Introduce and use shareability to guard faulting
KVM: guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodes
And then you can post the 1GiB series separately. So long as you provide pointers to dependencies along with a link to a repo+branch with the kitchen sink, I won't complain about things being too chaotic :-)
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Sean,
Quoted 64 lines · Sean Christopherson
Quoted 36 lines · Fuad Tabba
Quoted 35 lines · Vishal Annapurve
Quoted 8 lines · Fuad Tabba
Quoted 6 lines · Vishal Annapurve
On Wed, May 21, 2025 at 5:36 AM Fuad Tabba <[email protected]> wrote:
There are a bunch of complexities here, reboot sequence on x86 can be triggered using multiple ways that I don't fully understand, but few of them include reading/writing to "reset register" in MMIO/PCI config space that are emulated by the host userspace directly. Host has to know when the guest is shutting down to manage it's lifecycle.Quoted 5 lines
In that case, I think we need to fully understand these complexities before adding new IOCTLs. It could be that once we understand these issues, we find that we don't need these IOCTLs. It's hard to justify adding an IOCTL for something we don't understand.
I don't understand all the ways x86 guest can trigger reboot but I do know that x86 CoCo linux guest kernel triggers reset using MMIO/PCI config register write that is emulated by host userspace.
Quoted 15 lines
Quoted 11 lines
x86 CoCo VM firmwares don't support warm/soft reboot and even if it does in future, guest kernel can choose a different reboot mechanism. So guest reboot needs to be emulated by always starting from scratch. This sequence needs initial guest firmware payload to be installed into private ranges of
guest_memfd.Quoted 3 lines
Either the host doesn't (or cannot even) know that the guest is rebooting, in which case I don't see how having an IOCTL would help.
Host does know that the guest is rebooting.
In that case, that (i.e., the host finding out that the guest is rebooting) could trigger the conversion back to private. No need for an IOCTL.
Quoted 5 lines
Quoted 2 lines
In the reboot scenarios, it's the host userspace finding out that the guest kernel wants to reboot.
How does the host userspace find that out? If the host userspace is capable of finding that out, then surely KVM is also capable of finding out the same.
Nope, not on x86. Well, not without userspace invoking a new ioctl, which would defeat the purpose of adding these ioctls.
KVM is only responsible for emulating/virtualizing the "CPU". The chipset, e.g. the PCI config space, is fully owned by userspace. KVM doesn't even know whether or not PCI exists for the VM. And reboot may be emulated by simply creating a new KVM instance, i.e. even if KVM was somehow aware of the reboot request, the change in state would happen in an entirely new struct kvm.
That said, Vishal and Ackerley, this patch is a bit lacking on the documentation front. The changelog asserts that:
A
guest_memfdioctl is used because shareability is a property of the memory, and this property should be modifiable independently of the attached struct kvmbut then follows with a very weak and IMO largely irrelevant justification of:
This allows shareability to be modified even if the memory is not yet bound using memslots.
Quoted 6 lines
Allowing userspace to change shareability without memslots is one relatively minor flow in one very specific use case.
The real justification for these ioctls is that fundamentally, shareability for in-place conversions is a property of a
guest_memfdinstance and not a struct kvm instance, and so needs to owned byguest_memfd.
Thanks for the clarification Sean. I have a couple of followup questions/comments that you might be able to help with:
From a conceptual point of view, I understand that the in-place conversion is a property of guest_memfd. But that doesn't necessarily mean that the interface between kvm <-> guest_memfd is a userspace IOCTL. We already communicate directly between the two. Other, even less related subsystems within the kernel also interact without going through userspace. Why can't we do the same here? I'm not suggesting it not be owned by guest_memfd, but that we communicate directly.
From a performance point of view, I would expect the common case to be that when KVM gets an unshare request from the guest, it would be able to unmap those pages from the (cooperative) host userspace, and return back to the guest. In this scenario, the host userspace wouldn't even need to be involved. Having a userspace IOCTL as part of this makes that trip unnecessarily longer for the common case.
Cheers,
/fuad
Quoted 38 lines
I.e. focus on justifying the change from a design and conceptual perspective, not from a mechanical perspective of a flow that likely's somewhat unique to our specific environment. Y'all are getting deep into the weeds on a random aspect of x86 platform architecture, instead of focusing on the overall design.
The other issue that's likely making this more confusing than it needs to be is that this series is actually two completely different series bundled into one, with very little explanation. Moving shared vs. private ownership into
guest_memfdisn't a requirement for 1GiB support, it's a requirement for in-place shared/private conversion inguest_memfd.For the current
guest_memfdimplementation, shared vs. private is tracked in the VM via memory attributes, because aguest_memfdinstance is *only* private. I.e. shared vs. private is a property of the VM, not of theguest_memfdinstance. But when in-place conversion support comes along, ownership of that particular attribute needs to shift to theguest_memfdinstance.I know I gave feedback on earlier posting about there being too series flying around, but shoving two distinct concepts into a single series is not the answer. My complaints about too much noise wasn't that there were multiple series, it was that there was very little coordination and lots of chaos.
If you split this series in two, which should be trivial since you've already organized the patches as a split, then sans the selftests (thank you for those!), in-place conversion support will be its own (much smaller!) series that can focus on that specific aspect of the design, and can provide a cover letter that expounds on the design goals and uAPI.
KVM:
guest_memfd: Add CAPKVM_CAP_GMEM_CONVERSION
KVM: Queryguest_memfdfor private/shared status
KVM:guest_memfd: Skip LRU forguest_memfdfolios
KVM:guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
KVM:guest_memfd: Introduce and use shareability to guard faulting
KVM:guest_memfd: Make guest mem use guest mem inodes instead of anonymous inodesAnd then you can post the 1GiB series separately. So long as you provide pointers to dependencies along with a link to a repo+branch with the kitchen sink, I won't complain about things being too chaotic :-)
Sean Christopherson
maintainerRe: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 32 lines · Fuad Tabba
Quoted 24 lines · Sean Christopherson
Quoted 2 lines · Fuad Tabba
How does the host userspace find that out? If the host userspace is capable of finding that out, then surely KVM is also capable of finding out the same.
Nope, not on x86. Well, not without userspace invoking a new ioctl, which would defeat the purpose of adding these ioctls.
KVM is only responsible for emulating/virtualizing the "CPU". The chipset, e.g. the PCI config space, is fully owned by userspace. KVM doesn't even know whether or not PCI exists for the VM. And reboot may be emulated by simply creating a new KVM instance, i.e. even if KVM was somehow aware of the reboot request, the change in state would happen in an entirely new struct kvm.
That said, Vishal and Ackerley, this patch is a bit lacking on the documentation front. The changelog asserts that:
A
guest_memfdioctl is used because shareability is a property of the memory, and this property should be modifiable independently of the attached struct kvmbut then follows with a very weak and IMO largely irrelevant justification of:
This allows shareability to be modified even if the memory is not yet bound using memslots.
Quoted 6 lines
Allowing userspace to change shareability without memslots is one relatively minor flow in one very specific use case.
The real justification for these ioctls is that fundamentally, shareability for in-place conversions is a property of a
guest_memfdinstance and not a struct kvm instance, and so needs to owned byguest_memfd.
Quoted 6 lines
Thanks for the clarification Sean. I have a couple of followup questions/comments that you might be able to help with:
From a conceptual point of view, I understand that the in-place conversion is a property of
guest_memfd. But that doesn't necessarily mean that the interface between kvm <->guest_memfdis a userspace IOCTL.
kvm and guest_memfd aren't the communication endpoints for in-place conversions, and more importantly, kvm isn't part of the control plane. kvm's primary role (for guest_memfd with in-place conversions) is to manage the page tables to map memory into the guest.
kvm *may* also explicitly provide a communication channel between the guest and host, e.g. when conversions are initiated via hypercalls, but in some cases the communication channel may be created through pre-existing mechanisms, e.g. a shared memory buffer or emulated I/O (such as the PCI reset case).
guest => kvm (dumb pipe) => userspace => guest_memfd => kvm (invalidate)
And in other cases, kvm might not be in that part of the picture at all, e.g. if the userspace VMM provides an interface to the VM owner (which could also be the user running the VM) to reset the VM, then the flow would look like:
userspace => guest_memfd => kvm (invalidate)
A decent comparison is vCPUs. KVM _could_ route all ioctls through the VM, but that's unpleasant for all parties, as it'd be cumbersome for userspace, and unnecessarily complex and messy for KVM. Similarly, routing guest_memfd state changes through KVM_SET_MEMORY_ATTRIBUTES is awkward from both design and mechanical perspectives.
Even if we disagree on how ugly/pretty routing conversions through kvm would be, which I'll allow is subjective, the bigger problem is that bouncing through KVM_SET_MEMORY_ATTRIBUTES would create an unholy mess of an ABI.
Today, KVM_SET_MEMORY_ATTRIBUTES is handled entirely within kvm, and any changes take effect irrespective of any memslot bindings. And that didn't happen by chance; preserving and enforcing attribute changes independently of memslots was a key design requirement, precisely because memslots are ephemeral to a certain extent.
Adding support for in-place guest_memfd conversion will require new ABI, and so will be a "breaking" change for KVM_SET_MEMORY_ATTRIBUTES no matter what. E.g. KVM will need to reject KVM_MEMORY_ATTRIBUTE_PRIVATE for VMs that elect to use in-place guest_memfd conversions. But very critically, KVM can cripsly enumerate the lack of KVM_MEMORY_ATTRIBUTE_PRIVATE via KVM_CAP_MEMORY_ATTRIBUTES, the behavior will be very straightforward to document (e.g. CAP X is mutually excusive with KVM_MEMORY_ATTRIBUTE_PRIVATE), and it will be opt-in, i.e. won't truly be a breaking change.
If/when we move shareability to guest_memfd, routing state changes through KVM_SET_MEMORY_ATTRIBUTES will gain a subtle dependency on userspace having to create memslots in order for state changes to take effect. That wrinkle would be weird and annoying to document, e.g. "if CAP X is enabled, the ioctl ordering is A => B => C, otherwise the ordering doesn't matter", and would create many more conundrums:
- If a memslot needs to exist in order for KVM_SET_MEMORY_ATTRIBUTES to take effect, what should happen if that memslot is deleted?
- If a memslot isn't found, should KVM_SET_MEMORY_ATTRIBUTES fail and report an error, or silently do nothing?
- If KVM_SET_MEMORY_ATTRIBUTES affects multiple memslots that are bound to multiple guest_memfd, how does KVM guarantee atomicity? What happens if one guest_memfd conversion succeeds, but a later fails?
Quoted 4 lines
We already communicate directly between the two. Other, even less related subsystems within the kernel also interact without going through userspace. Why can't we do the same here? I'm not suggesting it not be owned by
guest_memfd, but that we communicate directly.
I'm not concerned about kvm communicating with guest_memfd, as you note it's all KVM. As above, my concerns are all about KVM's ABI and who owns/controls what.
Quoted 5 lines
From a performance point of view, I would expect the common case to be that when KVM gets an unshare request from the guest, it would be able to unmap those pages from the (cooperative) host userspace, and return back to the guest. In this scenario, the host userspace wouldn't even need to be involved.
Hard NAK, at least from an x86 perspective. Userspace is the sole decision maker with respect to what memory is state of shared vs. private, full stop. The guest can make *requests* to convert memory, but ultimately it's host userspace that decides whether or not to honor the request.
We've litigated this exact issue multiple times. All state changes must be controlled by userspace, because userspace is the only entity that can gracefully handle exceptions and edge cases, and is the only entity with (almost) full knowledge of the system. We can discuss this again if necessary, but I'd much prefer to not rehash all of those conversations.
Quoted 2 lines
Having a userspace IOCTL as part of this makes that trip unnecessarily longer for the common case.
I'm very skeptical that an exit to userspace is going to even be measurable in terms of the cost to convert memory. Conversion is going to require multiple locks, modifications to multiple sets of page tables with all the associated TLB maintenance, possibly cache maintenance, and probably a few other things I'm forgetting. The cost of a few user<=>kernel transitions is likely going to be a drop in the bucket.
If I'm wrong, and there are flows where the user<=>kernel transitions are the long pole, then we could certainly exploring adding a way for userspace to opt into a "fast path" conversion. But it would need to be exactly that, an optional fast path that can fall back to the "slow" userspace-driven conversion as needed.
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Hi Sean,
Quoted 134 lines · Sean Christopherson
Quoted 32 lines · Fuad Tabba
Quoted 24 lines · Sean Christopherson
Quoted 2 lines · Fuad Tabba
How does the host userspace find that out? If the host userspace is capable of finding that out, then surely KVM is also capable of finding out the same.
Nope, not on x86. Well, not without userspace invoking a new ioctl, which would defeat the purpose of adding these ioctls.
KVM is only responsible for emulating/virtualizing the "CPU". The chipset, e.g. the PCI config space, is fully owned by userspace. KVM doesn't even know whether or not PCI exists for the VM. And reboot may be emulated by simply creating a new KVM instance, i.e. even if KVM was somehow aware of the reboot request, the change in state would happen in an entirely new struct kvm.
That said, Vishal and Ackerley, this patch is a bit lacking on the documentation front. The changelog asserts that:
A
guest_memfdioctl is used because shareability is a property of the memory, and this property should be modifiable independently of the attached struct kvmbut then follows with a very weak and IMO largely irrelevant justification of:
This allows shareability to be modified even if the memory is not yet bound using memslots.
Quoted 6 lines
Allowing userspace to change shareability without memslots is one relatively minor flow in one very specific use case.
The real justification for these ioctls is that fundamentally, shareability for in-place conversions is a property of a
guest_memfdinstance and not a struct kvm instance, and so needs to owned byguest_memfd.Quoted 6 lines
Thanks for the clarification Sean. I have a couple of followup questions/comments that you might be able to help with:
From a conceptual point of view, I understand that the in-place conversion is a property of
guest_memfd. But that doesn't necessarily mean that the interface between kvm <->guest_memfdis a userspace IOCTL.kvm and
guest_memfdaren't the communication endpoints for in-place conversions, and more importantly, kvm isn't part of the control plane. kvm's primary role (forguest_memfdwith in-place conversions) is to manage the page tables to map memory into the guest.kvm *may* also explicitly provide a communication channel between the guest and host, e.g. when conversions are initiated via hypercalls, but in some cases the communication channel may be created through pre-existing mechanisms, e.g. a shared memory buffer or emulated I/O (such as the PCI reset case).
guest => kvm (dumb pipe) => userspace =>
guest_memfd=> kvm (invalidate)And in other cases, kvm might not be in that part of the picture at all, e.g. if the userspace VMM provides an interface to the VM owner (which could also be the user running the VM) to reset the VM, then the flow would look like:
userspace =>
guest_memfd=> kvm (invalidate)A decent comparison is vCPUs. KVM _could_ route all ioctls through the VM, but that's unpleasant for all parties, as it'd be cumbersome for userspace, and unnecessarily complex and messy for KVM. Similarly, routing
guest_memfdstate changes throughKVM_SET_MEMORY_ATTRIBUTESis awkward from both design and mechanical perspectives.Even if we disagree on how ugly/pretty routing conversions through kvm would be, which I'll allow is subjective, the bigger problem is that bouncing through
KVM_SET_MEMORY_ATTRIBUTESwould create an unholy mess of an ABI.Today,
KVM_SET_MEMORY_ATTRIBUTESis handled entirely within kvm, and any changes take effect irrespective of any memslot bindings. And that didn't happen by chance; preserving and enforcing attribute changes independently of memslots was a key design requirement, precisely because memslots are ephemeral to a certain extent.Adding support for in-place
guest_memfdconversion will require new ABI, and so will be a "breaking" change forKVM_SET_MEMORY_ATTRIBUTESno matter what. E.g. KVM will need to rejectKVM_MEMORY_ATTRIBUTE_PRIVATEfor VMs that elect to use in-placeguest_memfdconversions. But very critically, KVM can cripsly enumerate the lack ofKVM_MEMORY_ATTRIBUTE_PRIVATEviaKVM_CAP_MEMORY_ATTRIBUTES, the behavior will be very straightforward to document (e.g. CAP X is mutually excusive withKVM_MEMORY_ATTRIBUTE_PRIVATE), and it will be opt-in, i.e. won't truly be a breaking change.If/when we move shareability to
guest_memfd, routing state changes throughKVM_SET_MEMORY_ATTRIBUTESwill gain a subtle dependency on userspace having to create memslots in order for state changes to take effect. That wrinkle would be weird and annoying to document, e.g. "if CAP X is enabled, the ioctl ordering is A => B => C, otherwise the ordering doesn't matter", and would create many more conundrums:- If a memslot needs to exist in order for
KVM_SET_MEMORY_ATTRIBUTESto take effect, what should happen if that memslot is deleted?
- If a memslot isn't found, shouldKVM_SET_MEMORY_ATTRIBUTESfail and report an error, or silently do nothing?
- IfKVM_SET_MEMORY_ATTRIBUTESaffects multiple memslots that are bound to multipleguest_memfd, how does KVM guarantee atomicity? What happens if oneguest_memfdconversion succeeds, but a later fails?Quoted 4 lines
We already communicate directly between the two. Other, even less related subsystems within the kernel also interact without going through userspace. Why can't we do the same here? I'm not suggesting it not be owned by
guest_memfd, but that we communicate directly.I'm not concerned about kvm communicating with
guest_memfd, as you note it's all KVM. As above, my concerns are all about KVM's ABI and who owns/controls what.Quoted 5 lines
From a performance point of view, I would expect the common case to be that when KVM gets an unshare request from the guest, it would be able to unmap those pages from the (cooperative) host userspace, and return back to the guest. In this scenario, the host userspace wouldn't even need to be involved.
Hard NAK, at least from an x86 perspective. Userspace is the sole decision maker with respect to what memory is state of shared vs. private, full stop. The guest can make *requests* to convert memory, but ultimately it's host userspace that decides whether or not to honor the request.
We've litigated this exact issue multiple times. All state changes must be controlled by userspace, because userspace is the only entity that can gracefully handle exceptions and edge cases, and is the only entity with (almost) full knowledge of the system. We can discuss this again if necessary, but I'd much prefer to not rehash all of those conversations.
Quoted 2 lines
Having a userspace IOCTL as part of this makes that trip unnecessarily longer for the common case.
I'm very skeptical that an exit to userspace is going to even be measurable in terms of the cost to convert memory. Conversion is going to require multiple locks, modifications to multiple sets of page tables with all the associated TLB maintenance, possibly cache maintenance, and probably a few other things I'm forgetting. The cost of a few user<=>kernel transitions is likely going to be a drop in the bucket.
Quoted 4 lines
If I'm wrong, and there are flows where the user<=>kernel transitions are the long pole, then we could certainly exploring adding a way for userspace to opt into a "fast path" conversion. But it would need to be exactly that, an optional fast path that can fall back to the "slow" userspace-driven conversion as needed.
Thanks for this very thorough explanation. I know that we have litigated this issue, but not this _exact_ issue. My understanding was that the main reason for using IOCTLs for memory attributes is that userspace needs to manage private and shared memory seperately, including allocation and punching holes where necessary.
That said, no need to discuss this again. If it turns out that user<->kernel transitions are a bottleneck we could look into an opt-in fast path as you said.
Cheers,
/fuad
A message that is not in the archive (sent off-list or not yet received): [email protected]
Sean Christopherson
maintainerRe: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 1 line · Sean Christopherson
Trimmed Cc again.
And of course I forgot to actually Cc folks...
Quoted 53 lines · Sean Christopherson
Quoted 4 lines · Fuad Tabba
Quoted 3 lines
From a conceptual point of view, I understand that the in-place conversion is a property of
guest_memfd. But that doesn't necessarily mean that the interface between kvm <->guest_memfdis a userspace IOCTL....
Quoted 32 lines
A decent comparison is vCPUs. KVM _could_ route all ioctls through the VM, but that's unpleasant for all parties, as it'd be cumbersome for userspace, and unnecessarily complex and messy for KVM. Similarly, routing
guest_memfdstate changes throughKVM_SET_MEMORY_ATTRIBUTESis awkward from both design and mechanical perspectives.Even if we disagree on how ugly/pretty routing conversions through kvm would be, which I'll allow is subjective, the bigger problem is that bouncing through
KVM_SET_MEMORY_ATTRIBUTESwould create an unholy mess of an ABI.Today,
KVM_SET_MEMORY_ATTRIBUTESis handled entirely within kvm, and any changes take effect irrespective of any memslot bindings. And that didn't happen by chance; preserving and enforcing attribute changes independently of memslots was a key design requirement, precisely because memslots are ephemeral to a certain extent.Adding support for in-place
guest_memfdconversion will require new ABI, and so will be a "breaking" change forKVM_SET_MEMORY_ATTRIBUTESno matter what. E.g. KVM will need to rejectKVM_MEMORY_ATTRIBUTE_PRIVATEfor VMs that elect to use in-placeguest_memfdconversions. But very critically, KVM can cripsly enumerate the lack ofKVM_MEMORY_ATTRIBUTE_PRIVATEviaKVM_CAP_MEMORY_ATTRIBUTES, the behavior will be very straightforward to document (e.g. CAP X is mutually excusive withKVM_MEMORY_ATTRIBUTE_PRIVATE), and it will be opt-in, i.e. won't truly be a breaking change.If/when we move shareability to
guest_memfd, routing state changes throughKVM_SET_MEMORY_ATTRIBUTESwill gain a subtle dependency on userspace having to create memslots in order for state changes to take effect. That wrinkle would be weird and annoying to document, e.g. "if CAP X is enabled, the ioctl ordering is A => B => C, otherwise the ordering doesn't matter", and would create many more conundrums:Quoted 7 lines
- If a memslot needs to exist in order for
KVM_SET_MEMORY_ATTRIBUTESto take effect, what should happen if that memslot is deleted?
- If a memslot isn't found, shouldKVM_SET_MEMORY_ATTRIBUTESfail and report an error, or silently do nothing?
- IfKVM_SET_MEMORY_ATTRIBUTESaffects multiple memslots that are bound to multipleguest_memfd, how does KVM guarantee atomicity? What happens if oneguest_memfdconversion succeeds, but a later fails?Note, my above objections were purely about routing updates through a VM, e.g. due to having to resolve memslots and whatnot. I.e. I swear I'm not contradicting myself by suggesting we reuse
KVM_SET_MEMORY_ATTRIBUTESitself on the gmem file descriptor. I'm pretty sure past me didn't think at all about the actual uAPI, only the roles and responsibilities.
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
In reply to Vishal AnnapurveQuoted 110 lines · Vishal Annapurve
Quoted 63 lines · Fuad Tabba
Hi Vishal,
Quoted 55 lines · Vishal Annapurve
Quoted 46 lines · Fuad Tabba
Hi Ackerley,
Quoted 4 lines · Ackerley Tng
The two new
guest_memfdioctlsKVM_GMEM_CONVERT_SHAREDandKVM_GMEM_CONVERT_PRIVATEconvert the requested memory ranges to shared and private respectively.I have a high level question about this particular patch and this approach for conversion: why do we need IOCTLs to manage conversion between private and shared?
In the presentations I gave at LPC [1, 2], and in my latest patch series that performs in-place conversion [3] and the associated (by now outdated) state diagram [4], I didn't see the need to have a userspace-facing interface to manage that. KVM has all the information it needs to handle conversions, which are triggered by the guest. To me this seems like it adds additional complexity, as well as a user facing interface that we would need to maintain.
There are various ways we could handle conversion without explicit interference from userspace. What I had in mind is the following (as an example, details can vary according to VM type). I will use use the case of conversion from shared to private because that is the more complicated (interesting) case:
- Guest issues a hypercall to request that a shared folio become private.
- The hypervisor receives the call, and passes it to KVM.
- KVM unmaps the folio from the guest stage-2 (EPT I think in x86 parlance), and unmaps it from the host. The host however, could still have references (e.g., GUP).
- KVM exits to the host (hypervisor call exit), with the information that the folio has been unshared from it.
- A well behaving host would now get rid of all of its references (e.g., release GUPs), perform a VCPU run, and the guest continues running as normal. I expect this to be the common case.
But to handle the more interesting situation, let's say that the host doesn't do it immediately, and for some reason it holds on to some references to that folio.
Quoted 6 lines
- Even if that's the case, the guest can still run *. If the guest tries to access the folio, KVM detects that access when it tries to fault it into the guest, sees that the host still has references to that folio, and exits back to the host with a memory fault exit. At this point, the VCPU that has tried to fault in that particular folio cannot continue running as long as it cannot fault in that folio.
Quoted 3 lines
Are you talking about the following scheme?
1)guest_memfdchecks shareability on each get pfn and if there is a mismatch exit to the host.Quoted 5 lines
I think we are not really on the same page here (no pun intended :) ). I'll try to answer your questions anyway...
Which
get_pfn? Are you referring toget_pfnwhen faulting the page into the guest or into the host?I am referring to guest fault handling in KVM.
Quoted 23 lines
Quoted 3 lines
2) host user space has to guess whether it's a pending refcount or whether it's an actual mismatch.
No need to guess. VCPU run will let it know exactly why it's exiting.
Quoted 6 lines
3)
guest_memfdwill maintain a third state "pending_private_conversion" or equivalent which will transition to private upon the last refcount drop of each page.If conversion is triggered by userspace (in case of pKVM, it will be triggered from within the KVM (?)):
Why would conversion be triggered by userspace? As far as I know, it's the guest that triggers the conversion.
Quoted 6 lines
* Conversion will just fail if there are extra refcounts and userspace can try to get rid of extra refcounts on the range while it has enough context without hitting any ambiguity with memory fault exit.
*guest_memfdwill not have to deal with this extra state from 3 above and overallguest_memfdconversion handling becomes relatively simpler.Quoted 2 lines
That's not really related. The extra state isn't necessary any more once we agreed in the previous discussion that we will retry instead.
Who is *we* here? Which entity will retry conversion?
Quoted 9 lines
Quoted 4 lines
Note that for x86 CoCo cases, memory conversion is already triggered by userspace using KVM ioctl, this series is proposing to use
guest_memfdioctl to do the same.The reason why for x86 CoCo cases conversion is already triggered by userspace using KVM ioctl is that it has to, since shared memory and private memory are two separate pages, and userspace needs to manage that. Sharing memory in place removes the need for that.
Quoted 5 lines
Userspace still needs to clean up memory usage before conversion is successful. e.g. remove IOMMU mappings for shared to private conversion. I would think that memory conversion should not succeed before all existing users let go of the
guest_memfdpages for the range being converted.
Ah about that. Actually IOMMU mappings can remain the same in a case like mine TSM+VFIO RFC based on the Fuad's older patches, here in particular:
https://lore.kernel.org/r/[email protected]
which works nicely - mapped it once and forgot.
Now, I am rebasing my RFC on top of this patchset and it fails in kvm_gmem_has_safe_refcount() as IOMMU holds references to all these folios in my RFC.
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
For now (and for fun^wexperiment) I disabled kvm_gmem_has_safe_refcount() (04/51 adds it) and it seems to have no effect untiiil memfd is closed - folios_put_refs() crashes in list_del(&folio->lru). I wonder now what direction to go from here.
My TSM+VFIO RFC uses the hw ability to DMA to/from Coco VM (==AMD SEV SNP VM), both private and shared DMA at the same time is going to be allowed. Thanks,
Quoted 35 lines
In x86 CoCo usecases, userspace can also decide to not allow conversion for scenarios where ranges are still under active use by the host and guest is erroneously trying to take away memory. Both SNP/TDX spec allow failure of conversion due to in use memory.
Quoted 4 lines
This series isn't using the same ioctl, it's introducing new ones to perform a task that as far as I can tell so far, KVM can handle by itself.
I would like to understand this better. How will KVM handle the conversion process for
guest_memfdpages? Can you help walk an example sequence for shared to private conversion specifically aroundguest_memfdoffset states?Quoted 20 lines
Quoted 3 lines
- Allows not having to keep track of separate shared/private range
information in KVM.This patch series is already tracking shared/private range information in KVM.
Quoted 9 lines
- Simpler handling of the conversion process done per
guest_memfd
rather than for full range.
- Userspace can handle the rollback as needed, simplifying error
handling inguest_memfd.
-guest_memfdis single source of truth and notifies the users of
shareability change.
- e.g. IOMMU, userspace, KVM MMU all can be registered for
getting notifications fromguest_memfddirectly and will get notified for invalidation upon shareability attribute updates.All of these can still be done without introducing a new ioctl.
Cheers,
/fuad
Signature · 1 line
Alexey
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 9 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 10 lines · Jason Gunthorpe
Quoted 7 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely on guest_memfd for pinning -> no page refcounts taken by IOMMU stack
2) Directly query pfns from guest_memfd for both shared/private ranges
3) Implement an invalidation callback that guest_memfd can invoke on conversions.
Current flow:
Private to Shared conversion via kvm_gmem_convert_range() -
1) guest_memfd invokes kvm_gmem_invalidate_begin() for the ranges
on each bound memslot overlapping with the range
-> KVM has the concept of invalidation_begin() and end(),
which effectively ensures that between these function calls, no new EPT/NPT entries can be added for the range.
2) guest_memfd invokes kvm_gmem_convert_should_proceed() which
actually unmaps the KVM SEPT/NPT entries.
3) guest_memfd invokes kvm_gmem_execute_work() which updates the
shareability and then splits the folios if needed
Shared to private conversion via kvm_gmem_convert_range() -
1) guest_memfd invokes kvm_gmem_invalidate_begin() for the ranges
on each bound memslot overlapping with the range
2) guest_memfd invokes kvm_gmem_convert_should_proceed() which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
3) guest_memfd invokes kvm_gmem_execute_work() which updates the
shareability and then merges the folios if needed.
============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with guest_memfd ranges
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns from guest_memfd ranges using kvm_gmem_get_pfn()
-> kvm invokes kvm_gmem_is_private() to check for the range
shareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
* IOMMU stack exposes an invalidation callback that can be invoked by guest_memfd.
Private to Shared conversion via kvm_gmem_convert_range() -
1) guest_memfd invokes kvm_gmem_invalidate_begin() for the ranges
on each bound memslot overlapping with the range
2) guest_memfd invokes kvm_gmem_convert_should_proceed() which
actually unmaps the KVM SEPT/NPT entries.
-> guest_memfd invokes IOMMU invalidation callback to zap
the secure IOMMU entries.
3) guest_memfd invokes kvm_gmem_execute_work() which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.
Shared to private conversion via kvm_gmem_convert_range() -
1) guest_memfd invokes kvm_gmem_invalidate_begin() for the ranges
on each bound memslot overlapping with the range
2) guest_memfd invokes kvm_gmem_convert_should_proceed() which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
-> guest_memfd invokes IOMMU invalidation callback to zap the
non-secure IOMMU entries.
3) guest_memfd invokes kvm_gmem_execute_work() which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.
There should be a way to block external IOMMU pagetable updates while guest_memfd is performing conversion e.g. something like kvm_invalidate_begin()/end().
Quoted 4 lines
Quoted 2 lines
is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should
+1.
Quoted 7 lines
be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
Quoted 6 lines
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
+1.
Quoted 2 lines
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 40 lines · Vishal Annapurve
Quoted 10 lines · Jason Gunthorpe
Quoted 7 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stack
2) Directly query pfns fromguest_memfdfor both shared/private ranges
3) Implement an invalidation callback thatguest_memfdcan invoke on conversions.Current flow:
Private to Shared conversion viakvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range-> KVM has the concept of invalidation_begin() and end(),which effectively ensures that between these function calls, no new EPT/NPT entries can be added for the range.
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if neededShared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
Quoted 5 lines
============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdranges
Done that.
Quoted 2 lines
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns from
guest_memfdranges usingkvm_gmem_get_pfn()
This API imho should drop the confusing kvm_ prefix.
Quoted 4 lines
-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
Right now I cutnpasted kvm_gmem_get_folio() (which essentially is filemap_lock_folio()/filemap_alloc_folio()/__filemap_add_folio()) to avoid new links between iommufd.ko and kvm.ko. It is probably unavoidable though.
Quoted 21 lines
* IOMMU stack exposes an invalidation callback that can be invoked by
guest_memfd.Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
Quoted 5 lines
-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.
Alright (although this zap+map is not necessary on the AMD hw).
Quoted 10 lines
There should be a way to block external IOMMU pagetable updates while
guest_memfdis performing conversion e.g. something like kvm_invalidate_begin()/end().Quoted 4 lines
Quoted 2 lines
is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should
+1.
Yeah, not doing this already, because I physically cannot map gmemfd's memory in IOMMU via VMA (which allocates memory via gup() so wrong memory is mapped in IOMMU). Thanks,
Quoted 7 lines
Quoted 7 lines
be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
Quoted 8 lines
Quoted 6 lines
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
+1.
Alright. Thanks for the comments.
Quoted 3 lines
Quoted 3 lines
Jason
Signature · 1 line
Alexey
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 16 lines · Alexey Kardashevskiy
Quoted 12 lines · Vishal Annapurve
Quoted 10 lines · Jason Gunthorpe
Quoted 7 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
Quoted 6 lines
Quoted 6 lines
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stack
2) Directly query pfns fromguest_memfdfor both shared/private ranges
3) Implement an invalidation callback thatguest_memfdcan invoke on conversions.
Conversions and truncations both.
Quoted 33 lines
Quoted 22 lines
Current flow:
Private to Shared conversion viakvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range-> KVM has the concept of invalidation_begin() and end(),which effectively ensures that between these function calls, no new EPT/NPT entries can be added for the range.
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if neededShared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.Quoted 5 lines
============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdrangesDone that.
Quoted 2 lines
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns from
guest_memfdranges usingkvm_gmem_get_pfn()
Quoted 8 lines
This API imho should drop the confusing
kvm_prefix.Quoted 4 lines
-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
Right now I cutnpasted
kvm_gmem_get_folio()(which essentially is filemap_lock_folio()/filemap_alloc_folio()/__filemap_add_folio()) to avoid new links between iommufd.ko and kvm.ko. It is probably unavoidable though.
I don't think that's the way to avoid links between iommufd.ko and kvm.ko. Cleaner way probably is to have gmem logic built-in and allow runtime registration of invalidation callbacks from KVM/IOMMU backends. Need to think about this more.
Quoted 30 lines
Quoted 23 lines
* IOMMU stack exposes an invalidation callback that can be invoked by
guest_memfd.Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.Quoted 5 lines
-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.
Quoted 1 line
Alright (although this zap+map is not necessary on the AMD hw).
IMO guest_memfd ideally should not directly interact or cater to arch specific needs, it should implement a mechanism that works for all archs. KVM/IOMMU implement invalidation callbacks and have all the architecture specific knowledge to take the right decisions.
Quoted 41 lines
Quoted 6 lines
There should be a way to block external IOMMU pagetable updates while
guest_memfdis performing conversion e.g. something like kvm_invalidate_begin()/end().Quoted 6 lines
Quoted 4 lines
Quoted 2 lines
is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should
+1.
Yeah, not doing this already, because I physically cannot map gmemfd's memory in IOMMU via VMA (which allocates memory via
gup()so wrong memory is mapped in IOMMU). Thanks,Quoted 7 lines
Quoted 7 lines
be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
Quoted 8 lines
Quoted 6 lines
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
+1.
Alright. Thanks for the comments.
Quoted 3 lines
Quoted 3 lines
Jason
--
Alexey
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 66 lines · Vishal Annapurve
Quoted 16 lines · Alexey Kardashevskiy
Quoted 12 lines · Vishal Annapurve
Quoted 10 lines · Jason Gunthorpe
Quoted 7 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
Quoted 6 lines
Quoted 6 lines
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stack
2) Directly query pfns fromguest_memfdfor both shared/private ranges
3) Implement an invalidation callback thatguest_memfdcan invoke on conversions.Conversions and truncations both.
Quoted 40 lines
Quoted 22 lines
Current flow:
Private to Shared conversion viakvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range-> KVM has the concept of invalidation_begin() and end(),which effectively ensures that between these function calls, no new EPT/NPT entries can be added for the range.
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if neededShared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.Quoted 5 lines
============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdrangesDone that.
Quoted 2 lines
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns from
guest_memfdranges usingkvm_gmem_get_pfn()This API imho should drop the confusing
kvm_prefix.Quoted 4 lines
-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
Quoted 6 lines
Quoted 1 line
Right now I cutnpasted
kvm_gmem_get_folio()(which essentially is filemap_lock_folio()/filemap_alloc_folio()/__filemap_add_folio()) to avoid new links between iommufd.ko and kvm.ko. It is probably unavoidable though.I don't think that's the way to avoid links between iommufd.ko and kvm.ko. Cleaner way probably is to have gmem logic built-in and allow runtime registration of invalidation callbacks from KVM/IOMMU backends. Need to think about this more.
Yeah, otherwise iommufd.ko will have to install a hook in guest_memfd (==kvm.ko) in run time so more beloved symbol_get() :)
Quoted 31 lines
Quoted 31 lines
Quoted 29 lines
* IOMMU stack exposes an invalidation callback that can be invoked by
guest_memfd.Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.
Quoted 6 lines
Quoted 1 line
Alright (although this zap+map is not necessary on the AMD hw).
IMO
guest_memfdideally should not directly interact or cater to arch specific needs, it should implement a mechanism that works for all archs. KVM/IOMMU implement invalidation callbacks and have all the architecture specific knowledge to take the right decisions.
Every page conversion will go through:
kvm-amd.ko -1-> guest_memfd (kvm.ko) -2-> iommufd.ko -3-> amd-iommu (build-in).
Which one decides on IOMMU not needing (un)mapping? Got to be (1) but then it need to propagate the decision to amd-iommu (and we do not have (3) at the moment in that path).
Or we just always do unmap+map (and trigger unwanted page huge page smashing)? All is doable and neither particularly horrible, I'm trying to see where the consensus is now. Thanks,
Quoted 40 lines
Quoted 40 lines
Quoted 11 lines
There should be a way to block external IOMMU pagetable updates while
guest_memfdis performing conversion e.g. something like kvm_invalidate_begin()/end().Quoted 4 lines
Quoted 2 lines
is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should
+1.
Yeah, not doing this already, because I physically cannot map gmemfd's memory in IOMMU via VMA (which allocates memory via
gup()so wrong memory is mapped in IOMMU). Thanks,Quoted 7 lines
Quoted 7 lines
be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
Quoted 8 lines
Quoted 6 lines
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
+1.
Alright. Thanks for the comments.
Quoted 3 lines
Quoted 3 lines
Jason
--
Alexey
Signature · 1 line
Alexey
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 67 lines · Alexey Kardashevskiy
...
Quoted 18 lines
Quoted 18 lines
Quoted 5 lines
============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdrangesDone that.
Quoted 2 lines
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns from
guest_memfdranges usingkvm_gmem_get_pfn()This API imho should drop the confusing
kvm_prefix.Quoted 4 lines
-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
Quoted 6 lines
Quoted 1 line
Right now I cutnpasted
kvm_gmem_get_folio()(which essentially is filemap_lock_folio()/filemap_alloc_folio()/__filemap_add_folio()) to avoid new links between iommufd.ko and kvm.ko. It is probably unavoidable though.I don't think that's the way to avoid links between iommufd.ko and kvm.ko. Cleaner way probably is to have gmem logic built-in and allow runtime registration of invalidation callbacks from KVM/IOMMU backends. Need to think about this more.
Yeah, otherwise iommufd.ko will have to install a hook in
guest_memfd(==kvm.ko) in run time so more belovedsymbol_get():)Quoted 31 lines
Quoted 31 lines
Quoted 29 lines
* IOMMU stack exposes an invalidation callback that can be invoked by
guest_memfd.Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.Quoted 6 lines
Quoted 1 line
Alright (although this zap+map is not necessary on the AMD hw).
IMO
guest_memfdideally should not directly interact or cater to arch specific needs, it should implement a mechanism that works for all archs. KVM/IOMMU implement invalidation callbacks and have all the architecture specific knowledge to take the right decisions.
Quoted 5 lines
Every page conversion will go through:
kvm-amd.ko -1->
guest_memfd(kvm.ko) -2-> iommufd.ko -3-> amd-iommu (build-in).Which one decides on IOMMU not needing (un)mapping? Got to be (1) but then it need to propagate the decision to amd-iommu (and we do not have (3) at the moment in that path).
If there is a need, guest_memfd can support two different callbacks:
1) Conversion notifier/callback invoked by guest_memfd during conversion handling.
2) Invalidation notifier/callback invoked by guest_memfd during truncation.
Iommufd/kvm can handle conversion callback/notifier as per the needs of underlying architecture. e.g. for TDX connect do the unmapping vs for SEV Trusted IO skip the unmapping.
Invalidation callback/notifier will need to be handled by unmapping page tables.
Quoted 3 lines
Or we just always do unmap+map (and trigger unwanted page huge page smashing)? All is doable and neither particularly horrible, I'm trying to see where the consensus is now. Thanks,
I assume when you say huge page smashing, it means huge page NPT mapping getting split.
AFAIR, based on discussion with Michael during guest_memfd calls, stage2 NPT entries need to be of the same granularity as RMP tables for AMD SNP guests. i.e. huge page NPT mappings need to be smashed on the KVM side during conversion. So today guest_memfd sends invalidation notification to KVM for both conversion and truncation. Doesn't the same constraint for keeping IOMMU page tables at the same granularity as RMP tables hold for trusted IO?
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 92 lines · Vishal Annapurve
Quoted 67 lines · Alexey Kardashevskiy
...
Quoted 18 lines
Quoted 18 lines
Quoted 5 lines
============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdrangesDone that.
Quoted 2 lines
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns from
guest_memfdranges usingkvm_gmem_get_pfn()This API imho should drop the confusing
kvm_prefix.Quoted 4 lines
-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
Quoted 6 lines
Quoted 1 line
Right now I cutnpasted
kvm_gmem_get_folio()(which essentially is filemap_lock_folio()/filemap_alloc_folio()/__filemap_add_folio()) to avoid new links between iommufd.ko and kvm.ko. It is probably unavoidable though.I don't think that's the way to avoid links between iommufd.ko and kvm.ko. Cleaner way probably is to have gmem logic built-in and allow runtime registration of invalidation callbacks from KVM/IOMMU backends. Need to think about this more.
Yeah, otherwise iommufd.ko will have to install a hook in
guest_memfd(==kvm.ko) in run time so more belovedsymbol_get():)Quoted 31 lines
Quoted 31 lines
Quoted 29 lines
* IOMMU stack exposes an invalidation callback that can be invoked by
guest_memfd.Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.Quoted 6 lines
Quoted 1 line
Alright (although this zap+map is not necessary on the AMD hw).
IMO
guest_memfdideally should not directly interact or cater to arch specific needs, it should implement a mechanism that works for all archs. KVM/IOMMU implement invalidation callbacks and have all the architecture specific knowledge to take the right decisions.Quoted 5 lines
Every page conversion will go through:
kvm-amd.ko -1->
guest_memfd(kvm.ko) -2-> iommufd.ko -3-> amd-iommu (build-in).Which one decides on IOMMU not needing (un)mapping? Got to be (1) but then it need to propagate the decision to amd-iommu (and we do not have (3) at the moment in that path).
If there is a need,
guest_memfdcan support two different callbacks:
1) Conversion notifier/callback invoked byguest_memfdduring conversion handling.
2) Invalidation notifier/callback invoked byguest_memfdduring truncation.Iommufd/kvm can handle conversion callback/notifier as per the needs of underlying architecture. e.g. for TDX connect do the unmapping vs for SEV Trusted IO skip the unmapping.
Invalidation callback/notifier will need to be handled by unmapping page tables.
Quoted 3 lines
Or we just always do unmap+map (and trigger unwanted page huge page smashing)? All is doable and neither particularly horrible, I'm trying to see where the consensus is now. Thanks,
I assume when you say huge page smashing, it means huge page NPT mapping getting split.
Quoted 7 lines
AFAIR, based on discussion with Michael during
guest_memfdcalls, stage2 NPT entries need to be of the same granularity as RMP tables for AMD SNP guests. i.e. huge page NPT mappings need to be smashed on the KVM side during conversion. So todayguest_memfdsends invalidation notification to KVM for both conversion and truncation. Doesn't the same constraint for keeping IOMMU page tables at the same granularity as RMP tables hold for trusted IO?
Currently I handle this from the KVM with a hack to get IOPDE from AMD IOMMU so both 2MB RMP entry and IOPDE entries are smashed in one go in one of many firmwares running on EPYC, and atm this is too hacky to be posted even as an RFC. This likely needs to move to IOMMUFD then (via some callbacks) which could call AMD IOMMU which then would call that firmware (called "TMPM" and it is not the PSP which is "TSM), probably. Thanks,
Signature · 1 line
Alexey
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 7 lines · Alexey Kardashevskiy
Currently I handle this from the KVM with a hack to get IOPDE from AMD IOMMU so both 2MB RMP entry and IOPDE entries are smashed in one go in one of many firmwares running on EPYC, and atm this is too hacky to be posted even as an RFC. This likely needs to move to IOMMUFD then (via some callbacks) which could call AMD IOMMU which then would call that firmware (called "TMPM" and it is not the PSP which is "TSM), probably. Thanks,
Wasn't the issue with the iommu that it needed to have a PTE break whenever the shared/private changed in the RMP? Because the HW can't handle an IOPTE that crosses more than one RMP entry? Or do I misunderstand the problem?
If this is the problem I was expecting the page table code that translates the guest memfd into the iommu PTEs would respect the shared/private conversion boundaries and break up the PTEs automatically.
I had thought there were three versions of of how to copy from guest memfd into the IOPTEs:
- HW must never have a private physaddr in an IOPTE
- HW must have IOPTEs entirely private or shared
- HW handles everything and IOPTEs should be maximally sized
Is this right? Is AMD #2?
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
In reply to Vishal AnnapurveQuoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.
Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stack
In TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.
If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so guest_memfd can reject the request before attempting the actual unmap. Otherwise, the unmap of TD-DMA-pinned pages will fail.
Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Quoted 39 lines
2) Directly query pfns from
guest_memfdfor both shared/private ranges
3) Implement an invalidation callback thatguest_memfdcan invoke on conversions.Current flow:
Private to Shared conversion viakvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range-> KVM has the concept of invalidation_begin() and end(),which effectively ensures that between these function calls, no new EPT/NPT entries can be added for the range.
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if neededShared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdranges
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns fromguest_memfdranges usingkvm_gmem_get_pfn()-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
* IOMMU stack exposes an invalidation callback that can be invoked byguest_memfd.
Quoted 7 lines
Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
If guest_memfd could determine if a page is used by DMA purposes before attempting the actual unmaps, it could reject and fail the conversion earlier, thereby keeping IOMMU/S-EPT mappings intact.
This could prevent the conversion from partially failing.
Quoted 47 lines
3)
guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.There should be a way to block external IOMMU pagetable updates while
guest_memfdis performing conversion e.g. something like kvm_invalidate_begin()/end().Quoted 4 lines
Quoted 2 lines
is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should
+1.
Quoted 7 lines
be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
Quoted 6 lines
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
+1.
Quoted 2 lines
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 17 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stack
Quoted 8 lines
In TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap.
So it looks like guest_memfd will need an interface with KVM/IOMMU backends to check if unmapping can succeed. And if unmapping still fails, there should be a way for KVM/IOMMU backends to kill the TD and any TDIs bound to that TD.
Quoted 5 lines
Otherwise, the unmap of TD-DMA-pinned pages will fail.
Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 15 lines · Vishal Annapurve
Quoted 14 lines · Yan Zhao
Quoted 12 lines · Vishal Annapurve
Quoted 10 lines · Jason Gunthorpe
Quoted 7 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
Quoted 7 lines
Quoted 7 lines
Quoted 3 lines
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.
I don't see this as having much to do with iommufd.
iommufd will somehow support the T=1 iommu inside the TDX module but it won't have an IOAS for it since the VMM does not control the translation.
The discussion here is for the T=0 iommu which is controlled by iommufd and does have an IOAS. It should be popoulated with all the shared pages from the guestmemfd.
Quoted 3 lines
Quoted 3 lines
If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap.
This has to be delt with between the TDX module and KVM. When KVM gives pages to become secure it may not be able to get them back..
This problem has nothing to do with iommufd.
But generally I expect that the T=1 iommu follows the S-EPT entirely and there is no notion of pages "locked for dma". If DMA is ongoing and a page is made non-secure then the DMA fails.
Obviously in a mode where there is a vPCI device we will need all the pages to be pinned in the guestmemfd to prevent any kind of migrations. Only shared/private conversions should change the page around.
Maybe this needs to be an integral functionality in guestmemfd?
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 44 lines · Jason Gunthorpe
Quoted 15 lines · Vishal Annapurve
Quoted 14 lines · Yan Zhao
Quoted 12 lines · Vishal Annapurve
Quoted 10 lines · Jason Gunthorpe
Quoted 7 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
Quoted 7 lines
Quoted 7 lines
Quoted 3 lines
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.I don't see this as having much to do with iommufd.
iommufd will somehow support the T=1 iommu inside the TDX module but it won't have an IOAS for it since the VMM does not control the translation.
The discussion here is for the T=0 iommu which is controlled by iommufd and does have an IOAS. It should be popoulated with all the shared pages from the guestmemfd.
Quoted 3 lines
Quoted 3 lines
If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap.This has to be delt with between the TDX module and KVM. When KVM gives pages to become secure it may not be able to get them back..
This problem has nothing to do with iommufd.
Quoted 8 lines
But generally I expect that the T=1 iommu follows the S-EPT entirely and there is no notion of pages "locked for dma". If DMA is ongoing and a page is made non-secure then the DMA fails.
Obviously in a mode where there is a vPCI device we will need all the pages to be pinned in the guestmemfd to prevent any kind of migrations. Only shared/private conversions should change the page around.
Yes, guest_memfd ensures that all the faulted-in pages (irrespective of shared or private ranges) are not migratable. We already have a similar restriction with CPU accesses to encrypted memory ranges that need arch specific protocols to migrate memory contents.
Quoted 4 lines
Maybe this needs to be an integral functionality in guestmemfd?
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 26 lines · Vishal Annapurve
Quoted 25 lines · Jason Gunthorpe
Quoted 22 lines · Vishal Annapurve
Quoted 21 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.
Quoted 5 lines
Quoted 5 lines
I don't see this as having much to do with iommufd.
iommufd will somehow support the T=1 iommu inside the TDX module but it won't have an IOAS for it since the VMM does not control the translation.
I partially agree with this.
This is still the DMA Silent drop issue for security. The HW (Also applicable to AMD/ARM) screams out if the trusted DMA path (IOMMU mapping, or access control table like RMP) is changed out of TD's expectation. So from HW POV, it is the iommu problem.
For SW, if we don't blame iommu, maybe we rephrase as gmemfd can't invalidate private pages unless TD agrees.
Quoted 11 lines
Quoted 11 lines
The discussion here is for the T=0 iommu which is controlled by iommufd and does have an IOAS. It should be popoulated with all the shared pages from the guestmemfd.
Quoted 3 lines
Quoted 3 lines
If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap.This has to be delt with between the TDX module and KVM. When KVM gives pages to become secure it may not be able to get them back..
Just to be clear. With In-place conversion, it is not KVM gives pages to become secure, it is gmemfd. Or maybe you mean gmemfd is part of KVM.
https://lore.kernel.org/all/[email protected]/
Quoted 11 lines
Quoted 11 lines
This problem has nothing to do with iommufd.
But generally I expect that the T=1 iommu follows the S-EPT entirely and there is no notion of pages "locked for dma". If DMA is ongoing and a page is made non-secure then the DMA fails.
Obviously in a mode where there is a vPCI device we will need all the pages to be pinned in the guestmemfd to prevent any kind of migrations. Only shared/private conversions should change the page around.
Only *guest permitted* conversion should change the page. I.e only when VMM is dealing with the KVM_HC_MAP_GPA_RANGE hypercall. Not sure if we could just let QEMU ensure this or KVM/guestmemfd should ensure this.
Thanks,
Yilun
Quoted 11 lines
Yes,
guest_memfdensures that all the faulted-in pages (irrespective of shared or private ranges) are not migratable. We already have a similar restriction with CPU accesses to encrypted memory ranges that need arch specific protocols to migrate memory contents.Quoted 4 lines
Maybe this needs to be an integral functionality in guestmemfd?
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 33 lines · Xu Yilun
Quoted 26 lines · Vishal Annapurve
Quoted 25 lines · Jason Gunthorpe
Quoted 22 lines · Vishal Annapurve
Quoted 21 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.Quoted 5 lines
Quoted 5 lines
I don't see this as having much to do with iommufd.
iommufd will somehow support the T=1 iommu inside the TDX module but it won't have an IOAS for it since the VMM does not control the translation.
Quoted 6 lines
I partially agree with this.
This is still the DMA Silent drop issue for security. The HW (Also applicable to AMD/ARM) screams out if the trusted DMA path (IOMMU mapping, or access control table like RMP) is changed out of TD's expectation. So from HW POV, it is the iommu problem.
I thought the basic idea was that the secure world would sanity check what the insecure is doing and if it is not OK then it blows up. So if the DMA fails because the untrusted world revoked sharability when it should not have then this is correct and expected?
Quoted 2 lines
For SW, if we don't blame iommu, maybe we rephrase as gmemfd can't invalidate private pages unless TD agrees.
I think you mean guestmemfd in the kernel cannot autonomously change 'something' unless instructed to explicitly by userspace.
The expectation is the userspace will only give such instructions based on the VM telling it to do a shared/private change.
If userspace gives an instruction that was not agreed with the guest then the secure world can police the error and blow up.
Quoted 2 lines
Just to be clear. With In-place conversion, it is not KVM gives pages to become secure, it is gmemfd. Or maybe you mean gmemfd is part of KVM.
Yeah, I mean part of.
Quoted 8 lines
Quoted 4 lines
Quoted 4 lines
Obviously in a mode where there is a vPCI device we will need all the pages to be pinned in the guestmemfd to prevent any kind of migrations. Only shared/private conversions should change the page around.
Only *guest permitted* conversion should change the page. I.e only when VMM is dealing with the
KVM_HC_MAP_GPA_RANGEhypercall. Not sure if we could just let QEMU ensure this or KVM/guestmemfd should ensure this.
I think it should not be part of the kernel, no need. From a kernel perspective userspace has requested a shared/private conversion and if it wasn't agreed with the VM then it will explode.
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 36 lines · Jason Gunthorpe
Quoted 35 lines · Xu Yilun
Quoted 26 lines · Vishal Annapurve
Quoted 25 lines · Jason Gunthorpe
Quoted 22 lines · Vishal Annapurve
Quoted 21 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.Quoted 5 lines
Quoted 5 lines
I don't see this as having much to do with iommufd.
iommufd will somehow support the T=1 iommu inside the TDX module but it won't have an IOAS for it since the VMM does not control the translation.
I partially agree with this.
Quoted 7 lines
Quoted 4 lines
This is still the DMA Silent drop issue for security. The HW (Also applicable to AMD/ARM) screams out if the trusted DMA path (IOMMU mapping, or access control table like RMP) is changed out of TD's expectation. So from HW POV, it is the iommu problem.
I thought the basic idea was that the secure world would sanity check what the insecure is doing and if it is not OK then it blows up. So if
Yes. The secure world checks. But it let alone the unexpected change on CPU path cause CPU is synchronous and VM just pends on the fault, no security concern. While DMA is asynchronous and the secure world must blow up.
Quoted 2 lines
the DMA fails because the untrusted world revoked sharability when it should not have then this is correct and expected?
OK. From secure world POV the failing is correct & expected.
Quoted 4 lines
Quoted 3 lines
For SW, if we don't blame iommu, maybe we rephrase as gmemfd can't invalidate private pages unless TD agrees.
Quoted 8 lines
I think you mean guestmemfd in the kernel cannot autonomously change 'something' unless instructed to explicitly by userspace.
The expectation is the userspace will only give such instructions based on the VM telling it to do a shared/private change.
If userspace gives an instruction that was not agreed with the guest then the secure world can police the error and blow up.
Yes.
Quoted 11 lines
Quoted 3 lines
Just to be clear. With In-place conversion, it is not KVM gives pages to become secure, it is gmemfd. Or maybe you mean gmemfd is part of KVM.
Yeah, I mean part of.
Quoted 5 lines
Quoted 4 lines
Quoted 4 lines
Obviously in a mode where there is a vPCI device we will need all the pages to be pinned in the guestmemfd to prevent any kind of migrations. Only shared/private conversions should change the page around.
Quoted 7 lines
Quoted 3 lines
Only *guest permitted* conversion should change the page. I.e only when VMM is dealing with the
KVM_HC_MAP_GPA_RANGEhypercall. Not sure if we could just let QEMU ensure this or KVM/guestmemfd should ensure this.I think it should not be part of the kernel, no need. From a kernel perspective userspace has requested a shared/private conversion and if it wasn't agreed with the VM then it will explode.
I'm OK with it now. It's simple if we don't try to recover from the explosion. Although I see the after explosion processing in kernel is complex and not sure how it will advance.
Thanks,
Yilun
Quoted 2 lines
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
Only *guest permitted* conversion should change the page. I.e only when VMM is dealing with the
KVM_HC_MAP_GPA_RANGEhypercall. Not sure if we could just let QEMU ensure this or KVM/guestmemfd should ensure this.
Quoted 7 lines
Quoted 3 lines
I think it should not be part of the kernel, no need. From a kernel perspective userspace has requested a shared/private conversion and if it wasn't agreed with the VM then it will explode.
I'm OK with it now. It's simple if we don't try to recover from the explosion. Although I see the after explosion processing in kernel is complex and not sure how it will advance.
I see the discussion in another thread about similar issue. That TDX Module BUG causes S-EPT unmap impossible and just KVM_BUG_ON(). But this conversion issue is a little different, usually it's not decent to panic because of userspace request. So may need further error handling, or a KVM/gmemfd kAPI to disallow/allow conversion and prevent more complex error.
Thanks,
Yilun
Quoted 7 lines
Thanks,
YilunQuoted 2 lines
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
In reply to Yan ZhaoQuoted 21 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.
Quoted 9 lines
If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap. Otherwise, the unmap of TD-DMA-pinned pages will fail.Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Yan, Yilun, would it work if, on conversion,
1. guest_memfd notifies IOMMU that a conversion is about to happen for a PFN range
2. IOMMU forwards the notification to TDX code in the kernel
3. TDX code in kernel tells TDX module to stop thinking of any PFNs in the range as pinned for DMA?
If the above is possible then by the time we get to unmapping from S-EPTs, TDX module would already consider the PFNs in the range "not pinned for DMA".
Quoted 46 lines
Quoted 39 lines
2) Directly query pfns from
guest_memfdfor both shared/private ranges
3) Implement an invalidation callback thatguest_memfdcan invoke on conversions.Current flow:
Private to Shared conversion viakvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range-> KVM has the concept of invalidation_begin() and end(),which effectively ensures that between these function calls, no new EPT/NPT entries can be added for the range.
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if neededShared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.============================
For IOMMU, could something like below work?
* A new UAPI to bind IOMMU FDs with
guest_memfdranges
* VFIO_DMA_MAP/UNMAP operations modified to directly fetch pfns fromguest_memfdranges usingkvm_gmem_get_pfn()-> kvm invokes kvm_gmem_is_private() to check for the rangeshareability, IOMMU could use the same or we could add an API in gmem that takes in access type and checks the shareability before returning the pfn.
* IOMMU stack exposes an invalidation callback that can be invoked byguest_memfd.Quoted 7 lines
Private to Shared conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the KVM SEPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zapthe secure IOMMU entries.
Quoted 6 lines
If
guest_memfdcould determine if a page is used by DMA purposes before attempting the actual unmaps, it could reject and fail the conversion earlier, thereby keeping IOMMU/S-EPT mappings intact.This could prevent the conversion from partially failing.
If the above suggestion works, then instead of checking if pages are allowed to be unmapped, guest_memfd will just force everyone to unmap.
Quoted 47 lines
Quoted 47 lines
3)
guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then splits the folios if needed
4) Userspace invokes IOMMU map operation to map the ranges in
non-secure IOMMU.Shared to private conversion via
kvm_gmem_convert_range()-
1)guest_memfdinvokeskvm_gmem_invalidate_begin()for the ranges
on each bound memslot overlapping with the range
2)guest_memfdinvokeskvm_gmem_convert_should_proceed()which
actually unmaps the host mappings which will unmap the KVM non-seucure EPT/NPT entries.-> guest_memfd invokes IOMMU invalidation callback to zap thenon-secure IOMMU entries.
3)guest_memfdinvokeskvm_gmem_execute_work()which updates the
shareability and then merges the folios if needed.
4) Userspace invokes IOMMU map operation to map the ranges in secure IOMMU.There should be a way to block external IOMMU pagetable updates while
guest_memfdis performing conversion e.g. something like kvm_invalidate_begin()/end().Quoted 4 lines
Quoted 2 lines
is going to be flushed on a page conversion anyway (the RMPUPDATE instruction does that). All this is about AMD's x86 though.
The iommu should not be using the VMA to manage the mapping. It should
+1.
Quoted 7 lines
be directly linked to the guestmemfd in some way that does not disturb its operations. I imagine there would be some kind of invalidation callback directly to the iommu.
Presumably that invalidation call back can include a reason for the invalidation (addr change, shared/private conversion, etc)
Quoted 6 lines
I'm not sure how we will figure out which case is which but guestmemfd should allow the iommu to plug in either invalidation scheme..
Probably invalidation should be a global to the FD thing, I imagine that once invalidation is established the iommu will not be incrementing page refcounts.
+1.
Quoted 2 lines
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 33 lines · Ackerley Tng
Quoted 21 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.Quoted 9 lines
If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap. Otherwise, the unmap of TD-DMA-pinned pages will fail.Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Quoted 4 lines
Yan, Yilun, would it work if, on conversion,
1.
guest_memfdnotifies IOMMU that a conversion is about to happen for a PFN range
It is the Guest fw call to release the pinning. By the time VMM get the conversion requirement, the page is already physically unpinned. So I agree with Jason the pinning doesn't have to reach to iommu from SW POV.
Quoted 3 lines
2. IOMMU forwards the notification to TDX code in the kernel
3. TDX code in kernel tells TDX module to stop thinking of any PFNs in the range as pinned for DMA?
TDX host can't stop the pinning. Actually this mechanism is to prevent host from unpin/unmap the DMA out of Guest expectation.
Thanks,
Yilun
Quoted 4 lines
If the above is possible then by the time we get to unmapping from S-EPTs, TDX module would already consider the PFNs in the range "not pinned for DMA".
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 34 lines · Xu Yilun
Quoted 33 lines · Ackerley Tng
Quoted 30 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap. Otherwise, the unmap of TD-DMA-pinned pages will fail.Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Quoted 6 lines
Quoted 4 lines
Yan, Yilun, would it work if, on conversion,
1.
guest_memfdnotifies IOMMU that a conversion is about to happen for a PFN rangeIt is the Guest fw call to release the pinning.
I see, thanks for explaining.
Quoted 4 lines
By the time VMM get the
conversion requirement, the page is already physically unpinned. So I agree with Jason the pinning doesn't have to reach to iommu from SW POV.
If by the time KVM gets the conversion request, the page is unpinned, then we're all good, right?
When guest_memfd gets the conversion request, as part of conversion handling it will request to zap the page from stage-2 page tables. TDX module would see that the page is unpinned and the unmapping will proceed fine. Is that understanding correct?
Quoted 7 lines
Quoted 3 lines
2. IOMMU forwards the notification to TDX code in the kernel
3. TDX code in kernel tells TDX module to stop thinking of any PFNs in the range as pinned for DMA?TDX host can't stop the pinning. Actually this mechanism is to prevent host from unpin/unmap the DMA out of Guest expectation.
On this note, I'd also like to check something else. Putting TDX connect and IOMMUs aside, if the host unmaps a guest private page today without the guest requesting it, the unmapping will work and the guest will be broken, right?
Quoted 7 lines
Thanks,
YilunQuoted 4 lines
If the above is possible then by the time we get to unmapping from S-EPTs, TDX module would already consider the PFNs in the range "not pinned for DMA".
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 45 lines · Ackerley Tng
Quoted 34 lines · Xu Yilun
Quoted 33 lines · Ackerley Tng
Quoted 30 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap. Otherwise, the unmap of TD-DMA-pinned pages will fail.Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Quoted 6 lines
Quoted 4 lines
Yan, Yilun, would it work if, on conversion,
1.
guest_memfdnotifies IOMMU that a conversion is about to happen for a PFN rangeIt is the Guest fw call to release the pinning.
I see, thanks for explaining.
Quoted 7 lines
Quoted 4 lines
By the time VMM get the
conversion requirement, the page is already physically unpinned. So I agree with Jason the pinning doesn't have to reach to iommu from SW POV.If by the time KVM gets the conversion request, the page is unpinned, then we're all good, right?
Yes, unless guest doesn't unpin the page first by mistake. Guest would invoke a fw call tdg.mem.page.release to unpin the page before KVM_HC_MAP_GPA_RANGE.
Quoted 5 lines
When
guest_memfdgets the conversion request, as part of conversion handling it will request to zap the page from stage-2 page tables. TDX module would see that the page is unpinned and the unmapping will proceed fine. Is that understanding correct?
Yes, again unless guess doesn't unpin.
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
2. IOMMU forwards the notification to TDX code in the kernel
3. TDX code in kernel tells TDX module to stop thinking of any PFNs in the range as pinned for DMA?
Quoted 8 lines
Quoted 3 lines
TDX host can't stop the pinning. Actually this mechanism is to prevent host from unpin/unmap the DMA out of Guest expectation.
On this note, I'd also like to check something else. Putting TDX connect and IOMMUs aside, if the host unmaps a guest private page today without the guest requesting it, the unmapping will work and the guest will be broken, right?
Correct. The unmapping will work, the guest can't continue anymore.
Thanks,
Yilun
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 6 lines · Xu Yilun
Quoted 2 lines
If by the time KVM gets the conversion request, the page is unpinned, then we're all good, right?
Yes, unless guest doesn't unpin the page first by mistake. Guest would invoke a fw call tdg.mem.page.release to unpin the page before
KVM_HC_MAP_GPA_RANGE.
What does guest pinning mean?
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
Quoted 9 lines · Jason Gunthorpe
Quoted 6 lines · Xu Yilun
Quoted 2 lines
If by the time KVM gets the conversion request, the page is unpinned, then we're all good, right?
Yes, unless guest doesn't unpin the page first by mistake. Guest would invoke a fw call tdg.mem.page.release to unpin the page before
KVM_HC_MAP_GPA_RANGE.What does guest pinning mean?
TDX firmware provides a mode, that host can't block the S-EPT mapping after TD accepts the mapping. Guest 'pins' the private mapping (KVM & IOMMU).
TD should explicitly unaccept the page by tdg.mem.page.release, then host could successfully block/unmap the S-EPT. This is necessary when shared <-> private conversion.
When TDX Connect is enabled, this mode is enforced.
Thanks,
Yilun
Quoted 3 lines
Jason
Re: [RFC PATCH v2 04/51] KVM: guest_memfd: Introduce KVM_GMEM_CONVERT_SHARED/PRIVATE ioctls
In reply to Xu YilunQuoted 51 lines · Xu Yilun
Quoted 50 lines · Ackerley Tng
Quoted 34 lines · Xu Yilun
Quoted 33 lines · Ackerley Tng
Quoted 30 lines · Yan Zhao
Quoted 8 lines · Vishal Annapurve
Quoted 7 lines · Jason Gunthorpe
Quoted 4 lines · Alexey Kardashevskiy
Now, I am rebasing my RFC on top of this patchset and it fails in
kvm_gmem_has_safe_refcount()as IOMMU holds references to all these folios in my RFC.Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
So what is the expected sequence here? The userspace unmaps a DMA page and maps it back right away, all from the userspace? The end result will be the exactly same which seems useless. And IOMMU TLB
As Jason described, ideally IOMMU just like KVM, should just:
1) Directly rely onguest_memfdfor pinning -> no page refcounts taken by IOMMU stackIn TDX connect, TDX module and TDs do not trust VMM. So, it's the TDs to inform TDX module about which pages are used by it for DMAs purposes.
So, if a page is regarded as pinned by TDs for DMA, the TDX module will fail the unmap of the pages from S-EPT.If IOMMU side does not increase refcount, IMHO, some way to indicate that certain PFNs are used by TDs for DMA is still required, so
guest_memfdcan reject the request before attempting the actual unmap. Otherwise, the unmap of TD-DMA-pinned pages will fail.Upon this kind of unmapping failure, it also doesn't help for host to retry unmapping without unpinning from TD.
Quoted 6 lines
Quoted 4 lines
Yan, Yilun, would it work if, on conversion,
1.
guest_memfdnotifies IOMMU that a conversion is about to happen for a PFN rangeIt is the Guest fw call to release the pinning.
I see, thanks for explaining.
Quoted 4 lines
By the time VMM get the
conversion requirement, the page is already physically unpinned. So I agree with Jason the pinning doesn't have to reach to iommu from SW POV.
Quoted 4 lines
Quoted 2 lines
If by the time KVM gets the conversion request, the page is unpinned, then we're all good, right?
Yes, unless guest doesn't unpin the page first by mistake.
Or maliciously? :-(
My initial response to this was that this is a bug and we don't need to be concerned with it. However, can't this be a DOS from one TD to crash the system if the host uses the private page for something else and the machine #MC's?
Ira
Quoted 30 lines
Guest would
invoke a fw call tdg.mem.page.release to unpin the page beforeKVM_HC_MAP_GPA_RANGE.Quoted 5 lines
When
guest_memfdgets the conversion request, as part of conversion handling it will request to zap the page from stage-2 page tables. TDX module would see that the page is unpinned and the unmapping will proceed fine. Is that understanding correct?Yes, again unless guess doesn't unpin.
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
2. IOMMU forwards the notification to TDX code in the kernel
3. TDX code in kernel tells TDX module to stop thinking of any PFNs in the range as pinned for DMA?Quoted 8 lines
Quoted 3 lines
TDX host can't stop the pinning. Actually this mechanism is to prevent host from unpin/unmap the DMA out of Guest expectation.
On this note, I'd also like to check something else. Putting TDX connect and IOMMUs aside, if the host unmaps a guest private page today without the guest requesting it, the unmapping will work and the guest will be broken, right?
Correct. The unmapping will work, the guest can't continue anymore.
Thanks,
Yilun