KVM: mm: fd-based approach for supporting KVM guest private memory
Subsystems (23)
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 46 lines · Sean Christopherson
Quoted 24 lines · Ackerley Tng
One issue I’ve found so far is that the pointer to kvm (
gmem->kvm) is not cleaned up, and hence it is possible to crash the host kernel in the following way1. Create a KVM VM
2. Create a guest mem fd on that VM
3. Create a memslot with the guest mem fd (hence binding the fd to the VM)
4. Close/destroy the KVM VM
5. Callfallocate(PUNCH_HOLE)on the guest mem fd, which usesgmem->kvmwhen it tries to do invalidation.I then tried to clean up the
gmem->kvmpointer during unbinding when the KVM VM is destroyed.That works, but then I realized there’s a simpler way to use the pointer after freeing:
1. Create a KVM VM
2. Create a guest mem fd on that VM
3. Close/destroy the KVM VM
4. Callfallocate(PUNCH_HOLE)on the guest mem fd, which usesgmem->kvmwhen it tries to do invalidation.Quoted 3 lines
Perhaps binding should mean setting the
gmem->kvmpointer in addition togmem->bindings. This makes binding and unbinding symmetric and avoids the use-after-frees described above.Hrm, that would work, though it's a bit convoluted, e.g. would require detecting when the last binding is being removed. A similar (also ugly) solution would be to nullify
gmem->kvmwhen KVM dies.I don't love either approach idea because it means a file created in the context of a VM can outlive the VM itself, and then userspace ends up with a file descriptor that it can't do anything with except
close(). I doubt that matters in practice though, e.g. when the VM dies, all memory can be freed so that the file ends up being little more than a shell. And if we go that route, there's no need to grab a reference to the file during bind, KVM can just grab a longterm reference when the file is initially created and then drop it when KVM dies (and nullifiesgmem->kvm).Blech, another wart is that I believe gmem would need to do
__module_get()during file creation to prevent kvm.ko from being unloaded after the last VM dies. Ah, but that'd also be true if we went with a system-scoped KVMioctl(), so I suppose it's not _that_ ugly.
Quoted 8 lines
Exchanging references (at binding or at creation) doesn't work, because that creates a circular dependency, i.e. gmem and KVM would pin each other.
A "proper" refcounting approach, where the file pins KVM and not vice versa, gets nasty because of how KVM's memslots work. The least awful approach I can think of would be to delete the associated memslot(s) when the file is released, possibly via deferred work to avoid deadlock issues. Not the prettiest thing ever and in some ways that'd yield an even worse ABI.
Circling back to this. Pending testing, the "proper" refcounting approach seems to be the way to go. KVM's existing memslots actually work this way, e.g. if userspace does munmap() on an active memslot, KVM zaps any PTEs but the memslot stays alive. A similar approach can be taken here, the only wrinkle is that the association between gmem and the memslot is stronger than between VMAs and memslots, specifically KVM binds the file and not simply the file descriptor. This is necessary because not binding to an exact file would let userspace install a different file at the file descriptor.
That's solvable without having to delete memslots though, e.g. by protecting the file pointer in the memslot with RCU and directly bumping the refcount in the two places where KVM needs to get at gmem (the file) via the memslot (unbind and faulting in a page). E.g.
static struct file *kvm_gmem_get_file(struct kvm_memory_slot *slot)
{
struct file *file;
rcu_read_lock();
file = rcu_dereference(slot->gmem.file);
if (file && !get_file_rcu(file))
file = NULL;
rcu_read_unlock();
return file;}
The gotcha is that ->release could race with memslot deletion, as kvm_gmem_unbind() won't be able to differentiate between "file was deleted" and "file is currently being deleted". That's easy enough to deal with though, kvm_gmem_release() can take slots_lock to prevent the memslot from going away when nullifying and invalidating ranges for the memslot.
Quoted 4 lines
Side topic, there's a second bug (and probably more lurking): kvm_swap_active_memslots()'s call to
synchronize_srcu_expedited()is done _before_ the call tokvm_gmem_unbind(), i.e. doesn't wait for readers inkvm_gmem_invalidate_begin()to go away. The easy solution for that one is to add anothersynchronize_srcu_expedited()after unbinding.
There's a bug here, but not the one I pointed out. Acquiring kvm->srcu doesn't provide any protection, the binding already has a pointer to the memslot, i.e. isn't doing an SRCU-protected lookup in the memslots. The actual protection is provided by the filemap invalidate lock, which prevents unbinding a memslot until all invalidations complete, i.e. acquiring kvm->srcu in the punch hole path is completely unnecessary.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 19 lines · Sean Christopherson
Quoted 7 lines · Ackerley Tng
Hi Sean,
Thanks for implementing this POC!
... snip ...
I don't love either approach idea because it means a file created in the context of a VM can outlive the VM itself, and then userspace ends up with a file descriptor that it can't do anything with except
close(). I doubt that matters in practice though, e.g. when the VM dies, all memory can be freed so that the file ends up being little more than a shell. And if we go that route, there's no need to grab a reference to the file during bind, KVM can just grab a longterm reference when the file is initially created and then drop it when KVM dies (and nullifiesgmem->kvm).... snip ...
Quoted 8 lines
My preference is to make it a VM-scoped
ioctl(), if it ends up being a KVMioctl()and not a common syscall. If the file isn't tightly coupled to a single VM, then punching a hole is further complicated by needing to deal with invalidating multiple regions that are bound to different @kvm instances. It's not super complex, but AFAICT having theioctl()be system-scoped doesn't add value, e.g. I don't think having one VM own the memory will complicate even if/when we get to the point where VMs can share "private" memory, and the gmem code would still need to deal with grabbing a module reference.
I’d like to follow up on this discussion about a guest_mem file outliving the VM and whether to have a VM-scoped ioctl or a KVM ioctl.
Here's a POC of delayed binding of a guest_mem file to a memslot, where the guest_mem file outlives the VM [1].
I also hope to raise some points before we do the first integration of guest_mem patches!
A use case for guest_mem inodes outliving the VM is when the host VMM needs to be upgraded. The guest_mem inode is passed between two VMs on the same host machine and all memory associated with the inode needs to be retained.
To support the above use case, binding of memslots is delayed until first use, so that the following inode passing flow can be used:
1. Source (old version of host VMM) process passes guest_mem inodes to destination (new version of host VMM) process via unix sockets.
2. Destination process initializes memslots identical to source process.
3. Destination process invokes ioctl to migrate guest_mem inode over to destination process by unbinding all memslots from the source VM and binding them to the destination VM. (The kvm pointer is updated in this process)
Without delayed binding, step 2 will fail since initialization of memslots would check and find that the kvm pointer in the guest_mem inode points to the kvm in the source process.
These two patches contain the meat of the changes required to support delayed binding:
https://github.com/googleprodkernel/linux-cc/commit/93b31a006ef2e4dbe1ef0ec5d2534ca30f3bf60c
https://github.com/googleprodkernel/linux-cc/commit/dd5ac5e53f14a1ef9915c9c1e4cc1006a40b49df
Some things to highlight for the approach set out in these two patches:
1. Previously, closing the guest_mem file in userspace is taken to mean that all associated memory is to be removed and cleared. With these two patches, each memslot also holds a reference to the file (and therefore inode) and so even if the host VMM closes the fd, the VM will be able to continue to function.
This is desirable to userspace since closing the file should not be interpreted as a command to clear memory. This is aligned with the way tmpfs files are used with KVM before guest_mem: when the file is closed in userspace, the memory contents are still mapped and can still be used by the VM. fallocate(PUNCH_HOLE) is how userspace should command memory to be removed, just like munmap() would be used to remove memory from use by KVM.
2. Creating a guest mem file no longer depends on a specific VM and hence the guest_mem creation ioctl can be a system ioctl instead of a VM specific ioctl. This will also address Chao's concern at [3].
I also separated cleaning up files vs inodes in
https://github.com/googleprodkernel/linux-cc/commit/0f5aa18910c515141e57e05c4cc791022047a242,
which I believe is more aligned with how files and inodes are cleaned up in FSes. This alignment makes it easier to extend gmem to hugetlb, for one. While working on this, I was also wondering if we should perhaps be storing the inode pointer in slot->gmem instead of the file pointer? The memory is associated with an inode->mapping rather than the file. Are we binding to a userspace handle on the inode (store file pointer), or are we really referencing the inode (store inode pointer)?
The branch [1] doesn't handle the bug Sean previously mentioned at [2]:
Need to take a reference on the KVM module, so that even if guest_mem files are not bound to any VM, the KVM module cannot be unloaded. If the KVM module can be unloaded while guest_mem files are open, then userspace may be able to crash the kernel by invoking guest_mem functions that had been unloaded together with KVM.
[1] https://github.com/googleprodkernel/linux-cc/tree/gmem-delayed-binding
[2] https://lore.kernel.org/lkml/ZFWli2%[email protected]/
[3] https://lore.kernel.org/lkml/[email protected]/
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 33 lines · Ackerley Tng
Quoted 19 lines · Sean Christopherson
Quoted 7 lines · Ackerley Tng
Hi Sean,
Thanks for implementing this POC!
... snip ...
I don't love either approach idea because it means a file created in the context of a VM can outlive the VM itself, and then userspace ends up with a file descriptor that it can't do anything with except
close(). I doubt that matters in practice though, e.g. when the VM dies, all memory can be freed so that the file ends up being little more than a shell. And if we go that route, there's no need to grab a reference to the file during bind, KVM can just grab a longterm reference when the file is initially created and then drop it when KVM dies (and nullifiesgmem->kvm).... snip ...
Quoted 8 lines
My preference is to make it a VM-scoped
ioctl(), if it ends up being a KVMioctl()and not a common syscall. If the file isn't tightly coupled to a single VM, then punching a hole is further complicated by needing to deal with invalidating multiple regions that are bound to different @kvm instances. It's not super complex, but AFAICT having theioctl()be system-scoped doesn't add value, e.g. I don't think having one VM own the memory will complicate even if/when we get to the point where VMs can share "private" memory, and the gmem code would still need to deal with grabbing a module reference.I’d like to follow up on this discussion about a
guest_memfile outliving the VM and whether to have a VM-scoped ioctl or a KVM ioctl.
Quoted 8 lines
Here's a POC of delayed binding of a
guest_memfile to a memslot, where theguest_memfile outlives the VM [1].I also hope to raise some points before we do the first integration of
guest_mempatches!A use case for
guest_meminodes outliving the VM is when the host VMM needs to be upgraded.
Translation: to support intra-host migration, a.k.a. KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM
Quoted 27 lines
The
guest_meminode is passed between two VMs on the same host machine and all memory associated with the inode needs to be retained.To support the above use case, binding of memslots is delayed until first use, so that the following inode passing flow can be used:
1. Source (old version of host VMM) process passes
guest_meminodes to destination (new version of host VMM) process via unix sockets.
2. Destination process initializes memslots identical to source process.
3. Destination process invokes ioctl to migrateguest_meminode over to destination process by unbinding all memslots from the source VM and binding them to the destination VM. (The kvm pointer is updated in this process)Without delayed binding, step 2 will fail since initialization of memslots would check and find that the kvm pointer in the
guest_meminode points to the kvm in the source process.These two patches contain the meat of the changes required to support delayed binding:
https://github.com/googleprodkernel/linux-cc/commit/93b31a006ef2e4dbe1ef0ec5d2534ca30f3bf60c
https://github.com/googleprodkernel/linux-cc/commit/dd5ac5e53f14a1ef9915c9c1e4cc1006a40b49dfSome things to highlight for the approach set out in these two patches:
Quoted 8 lines
1. Previously, closing the
guest_memfile in userspace is taken to mean that all associated memory is to be removed and cleared. With these two patches, each memslot also holds a reference to the file (and therefore inode) and so even if the host VMM closes the fd, the VM will be able to continue to function.This is desirable to userspace since closing the file should not be interpreted as a command to clear memory.
100% agreed. However, after more thought since we first discussed this, I think that a deferred binding is the wrong way to solve this particular problem. More below.
Quoted 10 lines
This is aligned with the
way tmpfs files are used with KVM beforeguest_mem: when the file is closed in userspace, the memory contents are still mapped and can still be used by the VM.fallocate(PUNCH_HOLE)is how userspace should command memory to be removed, just likemunmap()would be used to remove memory from use by KVM.2. Creating a guest mem file no longer depends on a specific VM and hence the
guest_memcreation ioctl can be a system ioctl instead of a VM specific ioctl. This will also address Chao's concern at [3].
That concern is a non-issue for QEMU as memory backends are created after accelerators are initialized, and AFAICT Google's VMM behaves similarly.
And _if_ there is a VMM that instantiates memory before KVM_CREATE_VM, IMO making the ioctl() /dev/kvm scoped would have no meaningful impact on adapting userspace to play nice with the required ordering. If userspace can get at /dev/kvm, then it can do KVM_CREATE_VM, because the only input to KVM_CREATE_VM is the type, i.e. the only dependencies for KVM_CREATE_VM should be known/resolved long before the VMM knows it wants to use gmem.
Using a non-KVM syscall would eliminate any such dependencies, but in my very strong opinion, that is not a good reason to go with a syscall.
Quoted 4 lines
I also separated cleaning up files vs inodes in
https://github.com/googleprodkernel/linux-cc/commit/0f5aa18910c515141e57e05c4cc791022047a242,
which I believe is more aligned with how files and inodes are cleaned up in FSes.
I didn't take these, though I am in the process of incorporating parts of the underlying feedback (see below).
Quoted 1 line
This alignment makes it easier to extend gmem to hugetlb, for one.
I am not convinced that utilizing hugetlb is the best way to provide 1GiB support in gmem. I'm not necessarily against it, but there's a fair bit of uncertainty around the future of hugetlb, and there are fundamental aspects of hugetlb that may be non-goals for the gmem use case, e.g. mapping the memory with 1GiB pages in the userspace page tables likely isn't necessariy, and might even be undesirable.
And practically speaking, the changes aren't _that_ invasive, i.e. punting any necessary refactoring should not substantially increase the size/complexity of hugetlb support (*if* we end up adding it).
That said, I do think we'll implement .evict_inode() very early on in order to support SNP and TDX, because (in addition to PUNCH_HOLE) that's when the backing memory will be freed and thus reclaimed, i.e. unassigned in the RMP (SNP) / zeroed with the shared key ID (TDX).
Quoted 5 lines
While working on this, I was also wondering if we should perhaps be storing the inode pointer in
slot->gmeminstead of the file pointer? The memory is associated with aninode->mappingrather than the file. Are we binding to a userspace handle on the inode (store file pointer), or are we really referencing the inode (store inode pointer)?
Conceptually, I think KVM should to bind to the file. The inode is effectively the raw underlying physical storage, while the file is the VM's view of that storage.
Practically, I think that gives us a clean, intuitive way to handle intra-host migration. Rather than transfer ownership of the file, instantiate a new file for the target VM, using the gmem inode from the source VM, i.e. create a hard link. That'd probably require new uAPI, but I don't think that will be hugely problematic. KVM would need to ensure the new VM's guest_memfd can't be mapped until KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM (which would also need to verify the memslots/bindings are identical), but that should be easy enough to enforce.
That way, a VM, its memslots, and its SPTEs are tied to the file, while allowing the memory and the *contents* of memory to outlive the VM, i.e. be effectively transfered to the new target VM. And we'll maintain the invariant that each guest_memfd is bound 1:1 with a single VM.
As above, that should also help us draw the line between mapping memory into a VM (file), and freeing/reclaiming the memory (inode).
There will be extra complexity/overhead as we'll have to play nice with the possibility of multiple files per inode, e.g. to zap mappings across all files when punching a hole, but the extra complexity is quite small, e.g. we can use address_space.private_list to keep track of the guest_memfd instances associated with the inode.
Setting aside TDX and SNP for the moment, as it's not clear how they'll support memory that is "private" but shared between multiple VMs, I think per-VM files would work well for sharing gmem between two VMs. E.g. would allow a give page to be bound to a different gfn for each VM, would allow having different permissions for each file (e.g. to allow fallocate() only from the original owner).
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 7 lines · Sean Christopherson
...
And _if_ there is a VMM that instantiates memory beforeKVM_CREATE_VM, IMO making theioctl()/dev/kvmscoped would have no meaningful impact on adapting userspace to play nice with the required ordering. If userspace can get at/dev/kvm, then it can doKVM_CREATE_VM, because the only input toKVM_CREATE_VMis the type, i.e. the only dependencies forKVM_CREATE_VMshould be known/resolved long before the VMM knows it wants to use gmem.
I am not sure about the benefits of tying gmem creation to any given kvm instance. I think the most important requirement here is that a given gmem range is always tied to a single VM - This can be enforced when memslots are bound to the gmem files.
I believe "Required ordering" is that gmem files are created first and then supplied while creating the memslots whose gpa ranges can generate private memory accesses.
Is there any other ordering we want to enforce here?
Quoted 14 lines
...
Practically, I think that gives us a clean, intuitive way to handle intra-host migration. Rather than transfer ownership of the file, instantiate a new file for the target VM, using the gmem inode from the source VM, i.e. create a hard link. That'd probably require new uAPI, but I don't think that will be hugely problematic. KVM would need to ensure the new VM'sguest_memfdcan't be mapped untilKVM_CAP_VM_MOVE_ENC_CONTEXT_FROM(which would also need to verify the memslots/bindings are identical), but that should be easy enough to enforce.That way, a VM, its memslots, and its SPTEs are tied to the file, while allowing the memory and the *contents* of memory to outlive the VM, i.e. be effectively transfered to the new target VM. And we'll maintain the invariant that each
guest_memfdis bound 1:1 with a single VM.
Quoted 8 lines
As above, that should also help us draw the line between mapping memory into a VM (file), and freeing/reclaiming the memory (inode).
There will be extra complexity/overhead as we'll have to play nice with the possibility of multiple files per inode, e.g. to zap mappings across all files when punching a hole, but the extra complexity is quite small, e.g. we can use address_space.private_list to keep track of the
guest_memfdinstances associated with the inode.
Are we talking about a different usecase of sharing gmem fd across VMs other than intra-host migration?
If not, ideally only one of the files should be catering to the guest memory mappings at any given time. i.e. any inode should be ideally bound to (through the file) a single kvm instance, as we we are planning to ensure that guest_memfd can't be mapped until KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM is invoked on the target side.
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 11 lines · Vishal Annapurve
Quoted 7 lines · Sean Christopherson
...
And _if_ there is a VMM that instantiates memory beforeKVM_CREATE_VM, IMO making theioctl()/dev/kvmscoped would have no meaningful impact on adapting userspace to play nice with the required ordering. If userspace can get at/dev/kvm, then it can doKVM_CREATE_VM, because the only input toKVM_CREATE_VMis the type, i.e. the only dependencies forKVM_CREATE_VMshould be known/resolved long before the VMM knows it wants to use gmem.I am not sure about the benefits of tying gmem creation to any given kvm instance.
IMO, making gmem->kvm immutable is very nice to have, e.g. gmem->kvm will always be valid and the refcounting rules are fairly straightforward.
Quoted 2 lines
I think the most important requirement here is that a given gmem range is always tied to a single VM
I'm not convinced that that requirement will always hold true (see below).
Quoted 1 line
This can be enforced when memslots are bound to the gmem files.
Yeah, but TBH, waiting until the guest faults in memory to detect an invalid memslot is gross. And looking more closely, taking filemap_invalidate_lock(), i.e. taking a semaphore for write, in the page fault path is a complete non-starter. The "if (existing_slot == slot)" check is likely a non-starter, because KVM handles FLAGS_ONLY memslot updates, e.g. toggling dirty logging, by duplicating and replacing the memslot, not by updating the live memslot.
Quoted 4 lines
I believe "Required ordering" is that gmem files are created first and then supplied while creating the memslots whose gpa ranges can generate private memory accesses.
Is there any other ordering we want to enforce here?
I wasn't talking about enforcing arbitrary ordering, I was simply talking about what userspace literally needs to be able to do KVM_CREATE_GUEST_MEMFD.
Quoted 16 lines
Quoted 16 lines
Practically, I think that gives us a clean, intuitive way to handle intra-host migration. Rather than transfer ownership of the file, instantiate a new file for the target VM, using the gmem inode from the source VM, i.e. create a hard link. That'd probably require new uAPI, but I don't think that will be hugely problematic. KVM would need to ensure the new VM's
guest_memfdcan't be mapped untilKVM_CAP_VM_MOVE_ENC_CONTEXT_FROM(which would also need to verify the memslots/bindings are identical), but that should be easy enough to enforce.That way, a VM, its memslots, and its SPTEs are tied to the file, while allowing the memory and the *contents* of memory to outlive the VM, i.e. be effectively transfered to the new target VM. And we'll maintain the invariant that each
guest_memfdis bound 1:1 with a single VM.As above, that should also help us draw the line between mapping memory into a VM (file), and freeing/reclaiming the memory (inode).
Quoted 8 lines
Quoted 5 lines
There will be extra complexity/overhead as we'll have to play nice with the possibility of multiple files per inode, e.g. to zap mappings across all files when punching a hole, but the extra complexity is quite small, e.g. we can use address_space.private_list to keep track of the
guest_memfdinstances associated with the inode.Are we talking about a different usecase of sharing gmem fd across VMs other than intra-host migration?
Well, I am :-) I don't want to build all of this on an assumption that we'll never ever want to share a guest_memfd across multiple VMs. E.g. SEV (and SEV-ES?) already have the migration helper concept, and I've heard more than a few rumblings of TDX utilizing helper TDs. IMO, it's not far fetched at all to think that there will eventually be a need to let multiple VMs share a guest_memfd.
Quoted 3 lines
If not, ideally only one of the files should be catering to the guest memory mappings at any given time. i.e. any inode should be ideally bound to (through the file) a single kvm instance,
Why? Honest question, what does it buy us?
For TDX and SNP intra-host migration, it should be easy enough to ensure the new VM can't create mappings before migration, and that the old VM can't create mappings or run after migration. I don't see that being any harder if the source and dest use different files.
FWIW, it might be easier to hold off on this discussion until I post the RFC (which is going to happen on Monday at this point), as then we'll have actual code to discuss.
Quoted 2 lines
as we we are planning to ensure that
guest_memfdcan't be mapped untilKVM_CAP_VM_MOVE_ENC_CONTEXT_FROMis invoked on the target side.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Ackerley TngQuoted 6 lines · Ackerley Tng
Hi Sean,
Thanks for implementing this POC!
I’ve started porting the selftests (both Chao’s and those I added [1]).
Hi Sean/Ackerley,
Thanks for doing that. Overall making gmem a KVM ioctl() looks good to me and it should also play nice with Intel TDX. Besides what Ackerley mentioned below, I think we haven't discussed device assignment, which will be supported in not too long distance. Current VFIO_IOMMU_MAP_DMA consumes virtual address so that needs to be fixed for fd-based memory anyway, and the fix looks not related to whether this being a syscall() or a KVM ioctl(). There will be some initialization sequence dependency, e.g. if gmem is finally a VM-scope ioctl() then we need VM created first before can we map fd-based memory in VFIO, but that sounds not an issue at all.
I also see Vlastimil/David expressed their preference on ioctl. So maybe we can move forward on your current PoC. Do you already have a plan to post a formal version?
Chao
Quoted 46 lines
guest mem seems to cover the use cases that have been discussed and proposed so far, but I still need to figure out how gmem can work with
+ hugetlbfs
+ specification of/storing memory policy (for NUMA node bindings)
+ memory accounting - we may need to account for memory used separately, so that guest mem shows up separately on/proc/meminfoand similar places.One issue I’ve found so far is that the pointer to kvm (
gmem->kvm) is not cleaned up, and hence it is possible to crash the host kernel in the following way1. Create a KVM VM
2. Create a guest mem fd on that VM
3. Create a memslot with the guest mem fd (hence binding the fd to the VM)
4. Close/destroy the KVM VM
5. Callfallocate(PUNCH_HOLE)on the guest mem fd, which usesgmem->kvmwhen it tries to do invalidation.I then tried to clean up the
gmem->kvmpointer during unbinding when the KVM VM is destroyed.That works, but then I realized there’s a simpler way to use the pointer after freeing:
1. Create a KVM VM
2. Create a guest mem fd on that VM
3. Close/destroy the KVM VM
4. Callfallocate(PUNCH_HOLE)on the guest mem fd, which usesgmem->kvmwhen it tries to do invalidation.Perhaps binding should mean setting the
gmem->kvmpointer in addition togmem->bindings. This makes binding and unbinding symmetric and avoids the use-after-frees described above.This also means that creating a guest mem fd is no longer dependent on the VM. Perhaps we can make creating a gmem fd a system ioctl (like
KVM_GET_API_VERSIONandKVM_CREATE_VM) instead of a vm ioctl?[1]
https://lore.kernel.org/all/[email protected]/T/Ackerley
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 8 lines · Sean Christopherson
...
cold. I poked around a bit to see how we could avoid reinventing all of that infrastructure for fd-only memory, and the best idea I could come up with is basically a rehash of Kirill's very original "KVM protected memory" RFC[3], i.e. allow "mapping" fd-only memory, but ensure that memory is never actually present from hardware's perspective.
I am most likely missing a lot of context here and possibly venturing into an infeasible/already shot down direction here. But I would still like to get this discussed here before we move on.
I am wondering if it would make sense to implement restricted_mem/guest_mem file to expose both private and shared memory regions, inline with Kirill's original proposal now that the file implementation is controlled by KVM.
Thinking from userspace perspective:
1) Userspace creates guest mem files and is able to mmap them but all accesses to these files result into faults as no memory is allowed to be mapped into userspace VMM pagetables.
2) Userspace registers mmaped HVA ranges with KVM with additional KVM_MEM_PRIVATE flag
3) Userspace converts memory attributes and this memory conversion allows userspace to access shared ranges of the file because those are allowed to be faulted in from guest_mem. Shared to private conversion unmaps the file ranges from userspace VMM pagetables.
4) Granularity of userspace pagetable mappings for shared ranges will have to be dictated by KVM guest_mem file implementation.
Caveat here is that once private pages are mapped into userspace view.
Benefits here:
1) Userspace view remains consistent while still being able to use HVA ranges
2) It would be possible to use HVA based APIs from userspace to do things like binding.
3) Double allocation wouldn't be a concern since hva ranges and gpa ranges possibly map to the same HPA ranges.
Quoted 9 lines
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
[1] https://lore.kernel.org/all/[email protected]
[2] https://lore.kernel.org/all/20230418-anfallen-irdisch-6993a61be10b@brauner
[3] https://lore.kernel.org/linux-mm/[email protected]
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 33 lines · Vishal Annapurve
Quoted 8 lines · Sean Christopherson
...
cold. I poked around a bit to see how we could avoid reinventing all of that infrastructure for fd-only memory, and the best idea I could come up with is basically a rehash of Kirill's very original "KVM protected memory" RFC[3], i.e. allow "mapping" fd-only memory, but ensure that memory is never actually present from hardware's perspective.I am most likely missing a lot of context here and possibly venturing into an infeasible/already shot down direction here. But I would still like to get this discussed here before we move on.
I am wondering if it would make sense to implement restricted_mem/guest_mem file to expose both private and shared memory regions, inline with Kirill's original proposal now that the file implementation is controlled by KVM.
Thinking from userspace perspective:
1) Userspace creates guest mem files and is able to mmap them but all accesses to these files result into faults as no memory is allowed to be mapped into userspace VMM pagetables.
2) Userspace registers mmaped HVA ranges with KVM with additionalKVM_MEM_PRIVATEflag
3) Userspace converts memory attributes and this memory conversion allows userspace to access shared ranges of the file because those are allowed to be faulted in fromguest_mem. Shared to private conversion unmaps the file ranges from userspace VMM pagetables.
4) Granularity of userspace pagetable mappings for shared ranges will have to be dictated by KVMguest_memfile implementation.
Quoted 9 lines
Caveat here is that once private pages are mapped into userspace view.
Benefits here:
1) Userspace view remains consistent while still being able to use HVA ranges
2) It would be possible to use HVA based APIs from userspace to do things like binding.
3) Double allocation wouldn't be a concern since hva ranges and gpa ranges possibly map to the same HPA ranges.
I realize now that VFIO IOMMU mappings are not associated with userspace MMU in any way. So this approach does leave the gap of not being able to handle modifications of IOMMU mappings when HVA mappings are modified in userspace page tables. I guess this could be a good enough reason to give up on this option.
Quoted 9 lines
Quoted 9 lines
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
[1] https://lore.kernel.org/all/[email protected]
[2] https://lore.kernel.org/all/20230418-anfallen-irdisch-6993a61be10b@brauner
[3] https://lore.kernel.org/linux-mm/[email protected]
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Vishal AnnapurveQuoted 10 lines · Vishal Annapurve
Quoted 8 lines · Sean Christopherson
...
cold. I poked around a bit to see how we could avoid reinventing all of that infrastructure for fd-only memory, and the best idea I could come up with is basically a rehash of Kirill's very original "KVM protected memory" RFC[3], i.e. allow "mapping" fd-only memory, but ensure that memory is never actually present from hardware's perspective.
Quoted 2 lines
I am most likely missing a lot of context here and possibly venturing into an infeasible/already shot down direction here.
Both :-)
Quoted 11 lines
But I would still like to get this discussed here before we move on.
I am wondering if it would make sense to implement restricted_mem/guest_mem file to expose both private and shared memory regions, inline with Kirill's original proposal now that the file implementation is controlled by KVM.
Thinking from userspace perspective:
1) Userspace creates guest mem files and is able to mmap them but all accesses to these files result into faults as no memory is allowed to be mapped into userspace VMM pagetables.
Never mapping anything into the userspace page table is infeasible. Technically it's doable, but it'd effectively require all of the work of an fd-based approach (and probably significantly more), _and_ it'd require touching core mm code.
VMAs don't provide hva=>pfn information, they're the kernel's way of implementing the abstraction provided to userspace by mmap(), mprotect() etc. Among many other things, a VMA describes properties of what is mapped, e.g. hugetblfs versus anonymous, where memory is mapped (virtual address), how memory is mapped, e.g. RWX protections, etc. But a VMA doesn't track the physical address, that info is all managed through the userspace page tables.
To make it possible to allow userspace to mmap() but not access memory (without redoing how the kernel fundamentally manages virtual=>physical mappings), the simplest approach is to install PTEs into userspace page tables, but never mark them Present in hardware, i.e. prevent actually accessing the backing memory. This is is exactly what Kirill's series in link [3] below implemented.
Issues that led to us abandoning the "map with special !Present PTEs" approach:
- Using page tables, i.e. hardware defined structures, to track gfn=>pfn mappings is inefficient and inflexible compared to software defined structures, especially for the expected use cases for CoCo guests.
- The kernel wouldn't _easily_ be able to enforce a 1:1 page:guest association, let alone a 1:1 pfn:gfn mapping.
- Does not work for memory that isn't backed by 'struct page', e.g. if devices gain support for exposing encrypted memory regions to guests.
- Poking into the VMAs to convert memory would be likely be less performant due to using infrastructure that is much "heavier", e.g. would require taking mmap_lock for write.
In short, shoehorning this into mmap() requires fighting how the kernel works at pretty much every step, and in the end, adding e.g. fbind() is a lot easier.
Quoted 9 lines
2) Userspace registers mmaped HVA ranges with KVM with additional
KVM_MEM_PRIVATEflag
3) Userspace converts memory attributes and this memory conversion allows userspace to access shared ranges of the file because those are allowed to be faulted in fromguest_mem. Shared to private conversion unmaps the file ranges from userspace VMM pagetables.
4) Granularity of userspace pagetable mappings for shared ranges will have to be dictated by KVMguest_memfile implementation.
Quoted 8 lines
Caveat here is that once private pages are mapped into userspace view.
Benefits here:
1) Userspace view remains consistent while still being able to use HVA ranges
2) It would be possible to use HVA based APIs from userspace to do things like binding.
3) Double allocation wouldn't be a concern since hva ranges and gpa ranges possibly map to the same HPA ranges.
#3 isn't entirely correct. If a different process (call it "B") maps shared memory, and then the guest converts that memory from shared to private, the backing pages for the previously shared mapping will still be mapped by process B unless userspace also ensures process B also unmaps on conversion.
#3 is also a limiter. E.g. if a guest is primarly backed by 1GiB pages, keeping the 1GiB mapping is desirable if the guest converts a few KiB of memory to shared, and possibly even if the guest converts a few MiB of memory.
Quoted 8 lines
Quoted 8 lines
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
[1] https://lore.kernel.org/all/[email protected]
[2] https://lore.kernel.org/all/20230418-anfallen-irdisch-6993a61be10b@brauner
[3] https://lore.kernel.org/linux-mm/[email protected]
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 40 lines · Sean Christopherson
Quoted 10 lines · Vishal Annapurve
Quoted 8 lines · Sean Christopherson
...
cold. I poked around a bit to see how we could avoid reinventing all of that infrastructure for fd-only memory, and the best idea I could come up with is basically a rehash of Kirill's very original "KVM protected memory" RFC[3], i.e. allow "mapping" fd-only memory, but ensure that memory is never actually present from hardware's perspective.Quoted 2 lines
I am most likely missing a lot of context here and possibly venturing into an infeasible/already shot down direction here.
Both :-)
Quoted 11 lines
But I would still like to get this discussed here before we move on.
I am wondering if it would make sense to implement restricted_mem/guest_mem file to expose both private and shared memory regions, inline with Kirill's original proposal now that the file implementation is controlled by KVM.
Thinking from userspace perspective:
1) Userspace creates guest mem files and is able to mmap them but all accesses to these files result into faults as no memory is allowed to be mapped into userspace VMM pagetables.Never mapping anything into the userspace page table is infeasible. Technically it's doable, but it'd effectively require all of the work of an fd-based approach (and probably significantly more), _and_ it'd require touching core mm code.
VMAs don't provide hva=>pfn information, they're the kernel's way of implementing the abstraction provided to userspace by
mmap(),mprotect()etc. Among many other things, a VMA describes properties of what is mapped, e.g. hugetblfs versus anonymous, where memory is mapped (virtual address), how memory is mapped, e.g. RWX protections, etc. But a VMA doesn't track the physical address, that info is all managed through the userspace page tables.
Quoted 6 lines
To make it possible to allow userspace to
mmap()but not access memory (without redoing how the kernel fundamentally manages virtual=>physical mappings), the simplest approach is to install PTEs into userspace page tables, but never mark them Present in hardware, i.e. prevent actually accessing the backing memory. This is is exactly what Kirill's series in link [3] below implemented.
Maybe it's simpler to do when mmaped regions are backed with files.
I see that shmem has fault handlers for accesses to VMA regions associated with the files, In theory a file implementation can always choose to not allocate physical pages for such faults (similar to F_SEAL_FAULT_AUTOALLOCATE that was discussed earlier).
Quoted 8 lines
Issues that led to us abandoning the "map with special !Present PTEs" approach:
- Using page tables, i.e. hardware defined structures, to track gfn=>pfn mappings is inefficient and inflexible compared to software defined structures, especially for the expected use cases for CoCo guests.
- The kernel wouldn't _easily_ be able to enforce a 1:1 page:guest association, let alone a 1:1 pfn:gfn mapping.
Maybe KVM can ensure that each page of the guest_mem file is associated with a single memslot. HVAs when they are registered can be associated with offsets into guest_mem files.
Quoted 7 lines
- Does not work for memory that isn't backed by 'struct page', e.g. if devices gain support for exposing encrypted memory regions to guests.
- Poking into the VMAs to convert memory would be likely be less performant due to using infrastructure that is much "heavier", e.g. would require taking
mmap_lockfor write.
Converting memory doesn't necessarily need to poke holes into VMA, but rather just unmap pagetables just like what would happen when mmapped files are punched to free the backing file offsets.
Quoted 22 lines
In short, shoehorning this into
mmap()requires fighting how the kernel works at pretty much every step, and in the end, adding e.g.fbind()is a lot easier.Quoted 9 lines
2) Userspace registers mmaped HVA ranges with KVM with additional
KVM_MEM_PRIVATEflag
3) Userspace converts memory attributes and this memory conversion allows userspace to access shared ranges of the file because those are allowed to be faulted in fromguest_mem. Shared to private conversion unmaps the file ranges from userspace VMM pagetables.
4) Granularity of userspace pagetable mappings for shared ranges will have to be dictated by KVMguest_memfile implementation.Quoted 8 lines
Caveat here is that once private pages are mapped into userspace view.
Benefits here:
1) Userspace view remains consistent while still being able to use HVA ranges
2) It would be possible to use HVA based APIs from userspace to do things like binding.
3) Double allocation wouldn't be a concern since hva ranges and gpa ranges possibly map to the same HPA ranges.
Quoted 5 lines
#3 isn't entirely correct. If a different process (call it "B") maps shared memory, and then the guest converts that memory from shared to private, the backing pages for the previously shared mapping will still be mapped by process B unless userspace also ensures process B also unmaps on conversion.
This should be ideally handled by something like: unmap_mapping_range()
Quoted 3 lines
#3 is also a limiter. E.g. if a guest is primarly backed by 1GiB pages, keeping the 1GiB mapping is desirable if the guest converts a few KiB of memory to shared, and possibly even if the guest converts a few MiB of memory.
This caveat maybe can be lived with as shared ranges most likely will not be backed by 1G pages anyways, possibly causing IO performance to get hit. This possibly needs more discussion about conversion granularity used by guests.
Quoted 9 lines
Quoted 9 lines
Quoted 9 lines
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
[1] https://lore.kernel.org/all/[email protected]
[2] https://lore.kernel.org/all/20230418-anfallen-irdisch-6993a61be10b@brauner
[3] https://lore.kernel.org/linux-mm/[email protected]
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 31 lines · Vishal Annapurve
Quoted 23 lines · Sean Christopherson
Quoted 11 lines
But I would still like to get this discussed here before we move on.
I am wondering if it would make sense to implement restricted_mem/guest_mem file to expose both private and shared memory regions, inline with Kirill's original proposal now that the file implementation is controlled by KVM.
Thinking from userspace perspective:
1) Userspace creates guest mem files and is able to mmap them but all accesses to these files result into faults as no memory is allowed to be mapped into userspace VMM pagetables.Never mapping anything into the userspace page table is infeasible. Technically it's doable, but it'd effectively require all of the work of an fd-based approach (and probably significantly more), _and_ it'd require touching core mm code.
VMAs don't provide hva=>pfn information, they're the kernel's way of implementing the abstraction provided to userspace by
mmap(),mprotect()etc. Among many other things, a VMA describes properties of what is mapped, e.g. hugetblfs versus anonymous, where memory is mapped (virtual address), how memory is mapped, e.g. RWX protections, etc. But a VMA doesn't track the physical address, that info is all managed through the userspace page tables.Quoted 6 lines
To make it possible to allow userspace to
mmap()but not access memory (without redoing how the kernel fundamentally manages virtual=>physical mappings), the simplest approach is to install PTEs into userspace page tables, but never mark them Present in hardware, i.e. prevent actually accessing the backing memory. This is is exactly what Kirill's series in link [3] below implemented.
Quoted 6 lines
Maybe it's simpler to do when mmaped regions are backed with files.
I see that shmem has fault handlers for accesses to VMA regions associated with the files, In theory a file implementation can always choose to not allocate physical pages for such faults (similar to
F_SEAL_FAULT_AUTOALLOCATEthat was discussed earlier).
Ah, you're effectively suggesting a hybrid model where the file is the single source of truth for what's private versus shared, ad KVM gets pfns through direct communication with the backing store via the file descriptor, but userspace can still control things via mmap() and friends.
If you're not suggesting a backdoor, i.e. KVM still gets private pfns via hvas, then we're back at Kirill's series, because otherwise there's no easy way for KVM to retrieve the pfn.
A form of this was also discussed, though I don't know how much of the discussion happened on-list.
KVM actually does something like this for s390's Ultravisor (UV), which is quite a bit like TDX (UV is a trusted intermediary) except that it handles faults much, much more gracefully. Specifically, when the untrusted host attempts to access a secure page, a fault occurs and the kernel responds by telling UV to export the page. The fault is gracefully handled even even for kernel accesses (see do_secure_storage_access()). The kernel does BUG() if the export fails when handling fault from kernel context, but my understanding is that export can fail if and only if there's a fatal error elsewhere, i.e. the UV essentialy _ensures_ success, and goes straight to BUG()/panic() if something goes wrong.
On the guest side, accesses to exported (swapped) secure pages generate intercepts and KVM faults in the page. To do so, KVM freezes the page/folio refcount, tells the UV to import the page, and then unfreezes the page/folio. But very crucially, when _anything_ in the untrusted host attempts to access the secure page, the above fault handling for untrusted host accesses kicks in. In other words, the guest can cause thrash, but can't bring down the host.
TDX on the other hand silently poisons memory, i.e. doesn't even generate a synchronous fault. Thus the kernel needs to be 100% perfect on preventing _any_ accesses to private memory from the host, and doing that is non-trivial and invasive.
SNP does synchronously fault, but the automatically converting in the #PF handler got NAK'd[*] for good reasons, e.g. SNP doesn't guarantee conversion success as the guest can trigger concurrent RMP modifications. So the end result ends up being the same as TDX, host accesses need to be completely prevented.
Again, this is all doable, but costly. And IMO, provides very little value.
Allowing things like mbind() is nice-to-have at best, as implementing fbind() isn't straightforward and arguably valuable to have irrespective of this discussion, e.g. to allow userspace to say "use this policy regardless of what process maps the file".
Using a common memory pool (same physical page is used for both shared and private) is a similar story. There are plenty of existing controls to limit userspace/guest memory usage and to deal with OOM scenarios, so barring egregious host accounting and/or limiting bugs, which would affect _all_ VM types, the worst case scenario is that a VM is terminated because host userspace is buggy. On the slip side, using a common pool brings complexity into the kernel, as backing stores would need to be taught to deny access to a subset of pages in their mappings, and in multiple paths, e.g. faults, read()/write() and similar, page migration, swap, etc.
[*] https://lore.kernel.org/linux-mm/[email protected]
Quoted 11 lines
Quoted 8 lines
Issues that led to us abandoning the "map with special !Present PTEs" approach:
- Using page tables, i.e. hardware defined structures, to track gfn=>pfn mappings is inefficient and inflexible compared to software defined structures, especially for the expected use cases for CoCo guests.
- The kernel wouldn't _easily_ be able to enforce a 1:1 page:guest association, let alone a 1:1 pfn:gfn mapping.
Maybe KVM can ensure that each page of the
guest_memfile is associated with a single memslot.
This is a hard NAK. Guest physical address space is guaranteed to have holes and/or be discontiguous, for the PCI hole at the top of lower memory. Allowing only a single binding would prevent userspace from backing all (or large chunks) of guest memory with a single file.
Quoted 1 line
HVAs when they are registered can be associated with offsets into
guest_memfiles.
Enforcing 1:1 assocations is doable if KVM inserts a shim/interposer, e.g. essentially implements the exclusivity bits of restrictedmem. But that's adding even more complexity.
Quoted 10 lines
Quoted 6 lines
- Does not work for memory that isn't backed by 'struct page', e.g. if devices gain support for exposing encrypted memory regions to guests.
- Poking into the VMAs to convert memory would be likely be less performant due to using infrastructure that is much "heavier", e.g. would require taking
mmap_lockfor write.Converting memory doesn't necessarily need to poke holes into VMA, but rather just unmap pagetables just like what would happen when mmapped files are punched to free the backing file offsets.
Sorry, bad choice of word on my part. I didn't intend to imply poking holes, in this case I used "poking" to mean "modifying". munmap(), mprotected(), etc all require modifying VMAs, which means taking mmap_lock for write.
Quoted 21 lines
Quoted 21 lines
In short, shoehorning this into
mmap()requires fighting how the kernel works at pretty much every step, and in the end, adding e.g.fbind()is a lot easier.Quoted 17 lines
2) Userspace registers mmaped HVA ranges with KVM with additional
KVM_MEM_PRIVATEflag
3) Userspace converts memory attributes and this memory conversion allows userspace to access shared ranges of the file because those are allowed to be faulted in fromguest_mem. Shared to private conversion unmaps the file ranges from userspace VMM pagetables.
4) Granularity of userspace pagetable mappings for shared ranges will have to be dictated by KVMguest_memfile implementation.Caveat here is that once private pages are mapped into userspace view.
Benefits here:
1) Userspace view remains consistent while still being able to use HVA ranges
2) It would be possible to use HVA based APIs from userspace to do things like binding.
3) Double allocation wouldn't be a concern since hva ranges and gpa ranges possibly map to the same HPA ranges.
Quoted 7 lines
Quoted 5 lines
#3 isn't entirely correct. If a different process (call it "B") maps shared memory, and then the guest converts that memory from shared to private, the backing pages for the previously shared mapping will still be mapped by process B unless userspace also ensures process B also unmaps on conversion.
This should be ideally handled by something like:
unmap_mapping_range()
That'd work for the hybrid model (fd backdoor with pseudo mmap() support), but not for a generic VMA-based implementation. If the file isn't the single source of truth, then forcing all mappings to go away simply can't work.
Quoted 8 lines
Quoted 3 lines
#3 is also a limiter. E.g. if a guest is primarly backed by 1GiB pages, keeping the 1GiB mapping is desirable if the guest converts a few KiB of memory to shared, and possibly even if the guest converts a few MiB of memory.
This caveat maybe can be lived with as shared ranges most likely will not be backed by 1G pages anyways, possibly causing IO performance to get hit. This possibly needs more discussion about conversion granularity used by guests.
Yes, it's not the end of the world. My point is that separating shared and private memory provides more flexibility. Maybe that flexibility never ends up being super important, but at the same time we shouldn't willingly paint ourselves into a corner.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 10 lines · Sean Christopherson
...
Ah, you're effectively suggesting a hybrid model where the file is the single source of truth for what's private versus shared, ad KVM gets pfns through direct communication with the backing store via the file descriptor, but userspace can still control things viammap()and friends.If you're not suggesting a backdoor, i.e. KVM still gets private pfns via hvas, then we're back at Kirill's series, because otherwise there's no easy way for KVM to retrieve the pfn.
Yeah, I was hinting towards using the backdoor, where KVM still gets private pfns via HVAs.
Quoted 13 lines
A form of this was also discussed, though I don't know how much of the discussion happened on-list.
KVM actually does something like this for s390's Ultravisor (UV), which is quite a bit like TDX (UV is a trusted intermediary) except that it handles faults much, much more gracefully. Specifically, when the untrusted host attempts to access a secure page, a fault occurs and the kernel responds by telling UV to export the page. The fault is gracefully handled even even for kernel accesses (see
do_secure_storage_access()). The kernel doesBUG()if the export fails when handling fault from kernel context, but my understanding is that export can fail if and only if there's a fatal error elsewhere, i.e. the UV essentialy _ensures_ success, and goes straight to BUG()/panic() if something goes wrong.
Quoted 7 lines
On the guest side, accesses to exported (swapped) secure pages generate intercepts and KVM faults in the page. To do so, KVM freezes the page/folio refcount, tells the UV to import the page, and then unfreezes the page/folio. But very crucially, when _anything_ in the untrusted host attempts to access the secure page, the above fault handling for untrusted host accesses kicks in. In other words, the guest can cause thrash, but can't bring down the host.
Yeah, this is very similar to what I was trying to propose. Except in this case, the backing store i.e. guest_mem will have to let the fault be unhandled for untrusted host accesses to private ranges of guest_mem file.
Quoted 11 lines
TDX on the other hand silently poisons memory, i.e. doesn't even generate a synchronous fault. Thus the kernel needs to be 100% perfect on preventing _any_ accesses to private memory from the host, and doing that is non-trivial and invasive.
SNP does synchronously fault, but the automatically converting in the #PF handler got NAK'd[*] for good reasons, e.g. SNP doesn't guarantee conversion success as the guest can trigger concurrent RMP modifications. So the end result ends up being the same as TDX, host accesses need to be completely prevented.
Again, this is all doable, but costly. And IMO, provides very little value.
With this hybrid approach with the backdoor access to pfns from KVM, do we see a scenario where host can bypass the guest_mem restrictions and still be able to access the private ranges using HVA ranges? One possibility is that these pages are mapped in the IOMMU (when they are shared) and then get converted to private without getting unmapped from IOMMU. Maybe KVM can disallow converting the ranges which are pinned for DMA (not sure if there is a way to do that).
Few additional benefits here:
1) Possibly handle the pkvm usecase in this series without the need for additional modifications.
2) Handling UPM for normal VMs possibly could get simpler as this hybrid approach can allow preserving the contents across conversions.
Quoted 6 lines
Allowing things like
mbind()is nice-to-have at best, as implementingfbind()isn't straightforward and arguably valuable to have irrespective of this discussion, e.g. to allow userspace to say "use this policy regardless of what process maps the file".
Agreed, having mbind supported is not a significant gain given the cost here.
Quoted 8 lines
Using a common memory pool (same physical page is used for both shared and private) is a similar story. There are plenty of existing controls to limit userspace/guest memory usage and to deal with OOM scenarios, so barring egregious host accounting and/or limiting bugs, which would affect _all_ VM types, the worst case scenario is that a VM is terminated because host userspace is buggy. On the slip side, using a common pool brings complexity into the kernel, as backing stores would need to be taught to deny access to a subset of pages in their mappings, and in multiple paths, e.g. faults, read()/write() and similar, page migration, swap, etc.
In this case the backing store that needs to be modified would just be guest_mem though.
Quoted 12 lines
[*] https://lore.kernel.org/linux-mm/[email protected]
Quoted 9 lines
Quoted 8 lines
Issues that led to us abandoning the "map with special !Present PTEs" approach:
- Using page tables, i.e. hardware defined structures, to track gfn=>pfn mappings is inefficient and inflexible compared to software defined structures, especially for the expected use cases for CoCo guests.
- The kernel wouldn't _easily_ be able to enforce a 1:1 page:guest association, let alone a 1:1 pfn:gfn mapping.
Quoted 8 lines
Quoted 2 lines
Maybe KVM can ensure that each page of the
guest_memfile is associated with a single memslot.This is a hard NAK. Guest physical address space is guaranteed to have holes and/or be discontiguous, for the PCI hole at the top of lower memory. Allowing only a single binding would prevent userspace from backing all (or large chunks) of guest memory with a single file.
Poor choice of words from my side. I meant to suggest that KVM can ensure that ANY page of the guest_mem file is associated with at max one memslot.
Quoted 14 lines
...
That'd work for the hybrid model (fd backdoor with pseudommap()support), but not for a generic VMA-based implementation. If the file isn't the single source of truth, then forcing all mappings to go away simply can't work.Quoted 8 lines
Quoted 3 lines
#3 is also a limiter. E.g. if a guest is primarly backed by 1GiB pages, keeping the 1GiB mapping is desirable if the guest converts a few KiB of memory to shared, and possibly even if the guest converts a few MiB of memory.
This caveat maybe can be lived with as shared ranges most likely will not be backed by 1G pages anyways, possibly causing IO performance to get hit. This possibly needs more discussion about conversion granularity used by guests.
Quoted 4 lines
Yes, it's not the end of the world. My point is that separating shared and private memory provides more flexibility. Maybe that flexibility never ends up being super important, but at the same time we shouldn't willingly paint ourselves into a corner.
There are some performance implications here with the split approach.
This flexibility is actually coming with the cost of managing double allocation effectively. As the granularity of mappings increases for the shared memory, it gets difficult to cap the amount of double allocation. So effectively it comes down to always using smaller granularity for shared memory and also the private memory for converted ranges. In general the performance requirements would always try to push for higher mapping granularities depending on the scale of usage.
In general private memory (and also the shared memory on respective conversions) will always need to be hole punched to ensure that the double allocation won't happen. And so, even if this is something for the future, using hugetlbfs pages for backing private memory with the split model effectively makes it impossible to cap the double allocation. I am not sure if the 1G pages can be handled better with the hybrid model but maybe it's worth checking.
Split shared/private mem approach also increases the uncertainties around memory management in general where the same amount of memory which was available earlier is first freed to the system and then allocated back from the system. .e.g. Even if hugepages were around when private memory was initially allocated, further allocations keep increasing the possibilities of not being able to use a huge page to back the memory even if the whole huge page is private/shared.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 5 lines · Sean Christopherson
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
Hi Sean,
I've been working on getting the SNP patches ported to this but I'm having some trouble working out a reasonable scheme for how to work the RMPUPDATE hooks into the proposed design.
One of the main things is kvm_gmem_punch_hole(): this is can free pages back to the host whenever userspace feels like it. Pages that are still marked private in the RMP table will blow up the host if they aren't returned to the normal state before handing them back to the kernel. So I'm trying to add a hook, orchestrated by kvm_arch_gmem_invalidate(), to handle that, e.g.:
static long kvm_gmem_punch_hole(struct file *file, int mode, loff_t offset,
loff_t len)
{
struct kvm_gmem *gmem = file->private_data;
pgoff_t start = offset >> PAGE_SHIFT;
pgoff_t end = (offset + len) >> PAGE_SHIFT;
struct kvm *kvm = gmem->kvm; /*
* Bindings must stable across invalidation to ensure the start+end
* are balanced.
*/
filemap_invalidate_lock(file->f_mapping);
kvm_gmem_invalidate_begin(kvm, gmem, start, end);
/* Handle arch-specific cleanups before releasing pages */
kvm_arch_gmem_invalidate(kvm, gmem, start, end);
truncate_inode_pages_range(file->f_mapping, offset, offset + len);
kvm_gmem_invalidate_end(kvm, gmem, start, end);
filemap_invalidate_unlock(file->f_mapping);
return 0;}
But there's another hook, kvm_arch_gmem_set_mem_attributes(), needed to put the page in its intended state in the RMP table prior to mapping it into the guest's NPT. Currently I'm calling that hook via kvm_vm_ioctl_set_mem_attributes(), just after kvm->mem_attr_array is updated based on the ioctl. The reasoning there is that KVM MMU can then rely on the existing mmu_invalidate_seq logic to ensure both the state in the mem_attr_array and the RMP table are in sync and up-to-date once MMU lock is acquired and MMU is ready to map it, or retry #NPF otherwise.
But for kvm_gmem_punch_hole(), kvm_vm_ioctl_set_mem_attributes() can potentially result in something like the following sequence if I implement things as above:
CPU0: kvm_gmem_punch_hole():
kvm_gmem_invalidate_begin()
kvm_arch_gmem_invalidate() // set pages to default/shared state in RMP table before free'ing
CPU1: kvm_vm_ioctl_set_mem_attributes():
kvm_arch_gmem_set_mem_attributes() // maliciously set pages to private in RMP table
CPU0: truncate_inode_pages_range() // HOST BLOWS UP TOUCHING PRIVATE PAGES
kvm_arch_gmem_invalidate_end()
One quick and lazy solution is to rely on the fact that kvm_vm_ioctl_set_mem_attributes() holds the kvm->slots_lock throughout the entire begin()/end() portion of the invalidation sequence, and to similarly hold the kvm->slots_lock throughout the begin()/end() sequence in kvm_gmem_punch_hole() to prevent any interleaving.
But I'd imagine overloading kvm->slots_lock is not the proper approach. But would introducing a similar mutex to keep these operations grouped/atomic be a reasonable approach to you, or should we be doing something else entirely here?
Keep in mind that RMP updates can't be done while holding KVM->mmu_lock spinlock, because we also need to unmap pages from the directmap, which can lead to scheduling-while-atomic BUG()s[1], so that's another constraint we need to work around.
Thanks!
-Mike
[1] https://lore.kernel.org/linux-coco/[email protected]/T/#m45a1af063aa5ac0b9314d6a7d46eecb1253bba7a
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 43 lines · Michael Roth
Quoted 5 lines · Sean Christopherson
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
Hi Sean,
I've been working on getting the SNP patches ported to this but I'm having some trouble working out a reasonable scheme for how to work the RMPUPDATE hooks into the proposed design.
One of the main things is
kvm_gmem_punch_hole(): this is can free pages back to the host whenever userspace feels like it. Pages that are still marked private in the RMP table will blow up the host if they aren't returned to the normal state before handing them back to the kernel. So I'm trying to add a hook, orchestrated bykvm_arch_gmem_invalidate(), to handle that, e.g.:static long kvm_gmem_punch_hole(struct file *file, int mode, loff_t offset, loff_t len) { struct kvm_gmem *gmem = file->private_data; pgoff_t start = offset >> PAGE_SHIFT; pgoff_t end = (offset + len) >> PAGE_SHIFT; struct kvm *kvm = gmem->kvm;/* * Bindings must stable across invalidation to ensure the start+end * are balanced. */ filemap_invalidate_lock(file->f_mapping); kvm_gmem_invalidate_begin(kvm, gmem, start, end); /* Handle arch-specific cleanups before releasing pages */ kvm_arch_gmem_invalidate(kvm, gmem, start, end); truncate_inode_pages_range(file->f_mapping, offset, offset + len); kvm_gmem_invalidate_end(kvm, gmem, start, end); filemap_invalidate_unlock(file->f_mapping); return 0;
Quoted 5 lines
}
But there's another hook,
kvm_arch_gmem_set_mem_attributes(), needed to put the page in its intended state in the RMP table prior to mapping it into the guest's NPT.
IMO, this approach is wrong. kvm->mem_attr_array is the source of truth for whether userspace wants _guest_ physical pages mapped private vs. shared, but the attributes array has zero insight into the _host_ physical pages. I.e. SNP shouldn't hook kvm_mem_attrs_changed(), because operating on the RMP from that code is fundamentally wrong.
A good analogy is moving a memslot (ignoring that AFAIK no VMM actually moves memslots, but it's a good analogy for KVM internals). KVM needs to zap all mappings for the old memslot gfn, but KVM does not create mappings for the new memslot gfn. Same for changing attributes; unmap, but never map.
As for the unmapping side of things, kvm_unmap_gfn_range() will unmap all relevant NPT entries, and the elevated mmu_invalidate_in_progress will prevent KVM from establishing a new NPT mapping. And mmu_invalidate_in_progress will reach '0' only after both truncation _and_ kvm_vm_ioctl_set_mem_attributes() complete, i.e. KVM can create new mappings only when both kvm->mem_attr_array and any relevant guest_mem bindings have reached steady state.
That leaves the question of when/where to do RMP updates. Off the cuff, I think RMP updates (and I _think_ also TDX page conversions) should _always_ be done in the context of either (a) file truncation (make host owned due, a.k.a. TDX reclaim) or (b) allocating a new page/folio in guest_mem, a.k.a. kvm_gmem_get_folio(). Under the hood, even though the gfn is the same, the backing pfn is different, i.e. installing a shared mapping should _never_ need to touch the RMP because pages common from the normal (non-guest_mem) pool must already be host owned.
Quoted 44 lines
Currently I'm calling that hook via
kvm_vm_ioctl_set_mem_attributes(), just afterkvm->mem_attr_arrayis updated based on the ioctl. The reasoning there is that KVM MMU can then rely on the existingmmu_invalidate_seqlogic to ensure both the state in themem_attr_arrayand the RMP table are in sync and up-to-date once MMU lock is acquired and MMU is ready to map it, or retry #NPF otherwise.But for
kvm_gmem_punch_hole(),kvm_vm_ioctl_set_mem_attributes()can potentially result in something like the following sequence if I implement things as above:CPU0:
kvm_gmem_punch_hole():
kvm_gmem_invalidate_begin()
kvm_arch_gmem_invalidate()// set pages to default/shared state in RMP table before free'ing
CPU1:kvm_vm_ioctl_set_mem_attributes():
kvm_arch_gmem_set_mem_attributes()// maliciously set pages to private in RMP table
CPU0:truncate_inode_pages_range()// HOST BLOWS UP TOUCHING PRIVATE PAGES
kvm_arch_gmem_invalidate_end()One quick and lazy solution is to rely on the fact that
kvm_vm_ioctl_set_mem_attributes()holds thekvm->slots_lockthroughout the entire begin()/end() portion of the invalidation sequence, and to similarly hold thekvm->slots_lockthroughout the begin()/end() sequence inkvm_gmem_punch_hole()to prevent any interleaving.But I'd imagine overloading
kvm->slots_lockis not the proper approach. But would introducing a similar mutex to keep these operations grouped/atomic be a reasonable approach to you, or should we be doing something else entirely here?Keep in mind that RMP updates can't be done while holding
KVM->mmu_lockspinlock, because we also need to unmap pages from the directmap, which can lead to scheduling-while-atomic BUG()s[1], so that's another constraint we need to work around.Thanks!
-Mike
[1] https://lore.kernel.org/linux-coco/[email protected]/T/#m45a1af063aa5ac0b9314d6a7d46eecb1253bba7a
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 68 lines · Sean Christopherson
Quoted 43 lines · Michael Roth
Quoted 5 lines · Sean Christopherson
Code is available here if folks want to take a look before any kind of formal posting:
https://github.com/sean-jc/linux.git x86/kvm_gmem_solo
Hi Sean,
I've been working on getting the SNP patches ported to this but I'm having some trouble working out a reasonable scheme for how to work the RMPUPDATE hooks into the proposed design.
One of the main things is
kvm_gmem_punch_hole(): this is can free pages back to the host whenever userspace feels like it. Pages that are still marked private in the RMP table will blow up the host if they aren't returned to the normal state before handing them back to the kernel. So I'm trying to add a hook, orchestrated bykvm_arch_gmem_invalidate(), to handle that, e.g.:static long kvm_gmem_punch_hole(struct file *file, int mode, loff_t offset, loff_t len) { struct kvm_gmem *gmem = file->private_data; pgoff_t start = offset >> PAGE_SHIFT; pgoff_t end = (offset + len) >> PAGE_SHIFT; struct kvm *kvm = gmem->kvm;/* * Bindings must stable across invalidation to ensure the start+end * are balanced. */ filemap_invalidate_lock(file->f_mapping); kvm_gmem_invalidate_begin(kvm, gmem, start, end); /* Handle arch-specific cleanups before releasing pages */ kvm_arch_gmem_invalidate(kvm, gmem, start, end); truncate_inode_pages_range(file->f_mapping, offset, offset + len); kvm_gmem_invalidate_end(kvm, gmem, start, end); filemap_invalidate_unlock(file->f_mapping); return 0;Quoted 5 lines
}
But there's another hook,
kvm_arch_gmem_set_mem_attributes(), needed to put the page in its intended state in the RMP table prior to mapping it into the guest's NPT.IMO, this approach is wrong.
kvm->mem_attr_arrayis the source of truth for whether userspace wants _guest_ physical pages mapped private vs. shared, but the attributes array has zero insight into the _host_ physical pages. I.e. SNP shouldn't hookkvm_mem_attrs_changed(), because operating on the RMP from that code is fundamentally wrong.A good analogy is moving a memslot (ignoring that AFAIK no VMM actually moves memslots, but it's a good analogy for KVM internals). KVM needs to zap all mappings for the old memslot gfn, but KVM does not create mappings for the new memslot gfn. Same for changing attributes; unmap, but never map.
As for the unmapping side of things,
kvm_unmap_gfn_range()will unmap all relevant NPT entries, and the elevatedmmu_invalidate_in_progresswill prevent KVM from establishing a new NPT mapping. Andmmu_invalidate_in_progresswill reach '0' only after both truncation _and_kvm_vm_ioctl_set_mem_attributes()complete, i.e. KVM can create new mappings only when bothkvm->mem_attr_arrayand any relevantguest_membindings have reached steady state.
Quoted 7 lines
That leaves the question of when/where to do RMP updates. Off the cuff, I think RMP updates (and I _think_ also TDX page conversions) should _always_ be done in the context of either (a) file truncation (make host owned due, a.k.a. TDX reclaim) or (b) allocating a new page/folio in
guest_mem, a.k.a.kvm_gmem_get_folio(). Under the hood, even though the gfn is the same, the backing pfn is different, i.e. installing a shared mapping should _never_ need to touch the RMP because pages common from the normal (non-guest_mem) pool must already be host owned.
Hi Sean, thanks for the suggestions.
I reworked things based on this approach and things seems to work out pretty nicely for SNP.
I needed to add the hook to kvm_gmem_get_pfn() instead of kvm_gmem_get_folio() because SNP needs to know the GFN in order to mark the page as private in the RMP table, but otherwise I think things are the same as what you had in mind. One downside to this approach is since the hook always gets called during kvm_gmem_get_pfn(), we need to do an extra RMP lookup to determine whether or not that page has already been set to private state, vs. being able to assume it's already been put in the expected state, but it's only a memory access so not a huge overhead. Not sure if that would be a concern of not on the TDX side though.
I put together a tree with some fixups that are needed for against the kvm_gmem_solo base tree, and a set of hooks to handle invalidations, preparing the initial private state as suggested above, and a platform-configurable mask that the x86 MMU code can use for determining whether a fault is for private vs. shared pages.
KVM: x86: Determine shared/private faults using a configurable mask ^ for TDX we could trivially add an inverted analogue of the mask/logic
KVM: x86: Use full 64-bit error code for kvm_mmu_do_page_fault
KVM: x86: Add platform hooks for private memory invalidations
KVM: x86: Add platform hook for initializing private memory
*fixup (kvm_gmem_solo): KVM: Fix end range calculation for MMU invalidations *fixup (kvm_gmem_solo): KVM: selftests: update kvm_create_guest_memfd struct usage
https://github.com/mdroth/linux/commits/kvm_gmem_solo_x86
I'm hoping these are similarly usable for TDX, but could use some input from TDX folks on that aspect.
Quoted 5 lines
Quoted 5 lines
Keep in mind that RMP updates can't be done while holding
KVM->mmu_lockspinlock, because we also need to unmap pages from the directmap, which can lead to scheduling-while-atomic BUG()s[1], so that's another constraint we need to work around.
This concern also ends up going away since GFP_RECLAIM also has similar issues when called under kvm->mmu_lock, so having the hook in kvm_gmem_get_pfn() sort of guarantees we wouldn't hit issues with this.
-Mike
Quoted 11 lines
Quoted 11 lines
Thanks!
-Mike
[1] https://lore.kernel.org/linux-coco/[email protected]/T/#m45a1af063aa5ac0b9314d6a7d46eecb1253bba7a
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 8 lines · Michael Roth
Quoted 1 line · Sean Christopherson
On Thu, May 11, 2023, Michael Roth wrote:
I put together a tree with some fixups that are needed for against the
kvm_gmem_solobase tree, and a set of hooks to handle invalidations, preparing the initial private state as suggested above, and a platform-configurable mask that the x86 MMU code can use for determining whether a fault is for private vs. shared pages.
Quoted 4 lines
KVM: x86: Determine shared/private faults using a configurable mask ^ for TDX we could trivially add an inverted analogue of the mask/logic
KVM: x86: Use full 64-bit error code forkvm_mmu_do_page_fault
KVM: x86: Add platform hooks for private memory invalidations
Hrm, I'd prefer to avoid adding another hook for this case, arch code already has a "hook" in the form of kvm_unmap_gfn_range(). We'd probably just need a kvm_gfn_range.is_private flag to communicate to arch/vendor code that the memory being zapped is private.
That'd leave a gap for the unbind() case because kvm_unmap_gfn_range() is invoked if and only if there's an overlapping memslot. I'll chew on that a bit to see if there's a way to cleanly handle that case without another hook. I think it's worth mapping out exactly what we want unbind() to look like anyways, e.g. right now the code subtly relies on private memslots being immutable.
Quoted 1 line
KVM: x86: Add platform hook for initializing private memory
This should also be unnecessary. The call to kvm_gmem_get_pfn() is from arch code, KVM just needs to ensure the RMP is converted before acquiring mmu_lock, e.g. KVM has all the necessary info in kvm_tdp_mmu_page_fault().
The only reason to add another arch hook would be if we wanted to converted the RMP when _allocating_, e.g. to preconvert in response to fallocate() instead of waiting until #NPF. But I think I would rather add a generic ioctl() to allow userspace to effectively prefault guest memory, e.g. to setup the RMP before running a vCPU. Such an ioctl() would potentially be useful in other scenarios, e.g. on the dest during live migration to reduce jitter.
Quoted 1 line
*fixup (
kvm_gmem_solo): KVM: Fix end range calculation for MMU invalidations
There was another bug in this path. The math for handling a non-zero offsets into the file was wrong. The code now looks like:
xa_for_each_range(&gmem->bindings, index, slot, start, end - 1) {
struct kvm_gfn_range gfn_range = {
.start = slot->base_gfn + start - slot->gmem.index,
.end = slot->base_gfn + min(end - slot->gmem.index, slot->npages),
.slot = slot,
.pte = __pte(0),
.may_block = true,
};
if (WARN_ON_ONCE(start < slot->gmem.index ||
end > slot->gmem.index + slot->npages))
continue;
kvm_mmu_invalidate_range_add(kvm, gfn_range.start, gfn_range.end);
flush |= kvm_unmap_gfn_range(kvm, &gfn_range);
}Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 14 lines · Sean Christopherson
Quoted 8 lines · Michael Roth
Quoted 1 line · Sean Christopherson
On Thu, May 11, 2023, Michael Roth wrote:
I put together a tree with some fixups that are needed for against the
kvm_gmem_solobase tree, and a set of hooks to handle invalidations, preparing the initial private state as suggested above, and a platform-configurable mask that the x86 MMU code can use for determining whether a fault is for private vs. shared pages.Quoted 4 lines
KVM: x86: Determine shared/private faults using a configurable mask ^ for TDX we could trivially add an inverted analogue of the mask/logic
KVM: x86: Use full 64-bit error code forkvm_mmu_do_page_fault
KVM: x86: Add platform hooks for private memory invalidations
Quoted 4 lines
Hrm, I'd prefer to avoid adding another hook for this case, arch code already has a "hook" in the form of
kvm_unmap_gfn_range(). We'd probably just need a kvm_gfn_range.is_private flag to communicate toarch/vendorcode that the memory being zapped is private.
kvm_unmap_gfn_range() does however get called with kvm->mmu_lock held so it might be tricky to tie RMP updates into that path.
Quoted 6 lines
That'd leave a gap for the
unbind()case becausekvm_unmap_gfn_range()is invoked if and only if there's an overlapping memslot. I'll chew on that a bit to see if there's a way to cleanly handle that case without another hook. I think it's worth mapping out exactly what we wantunbind()to look like anyways, e.g. right now the code subtly relies on private memslots being immutable.
I thought the direction you sort of driving at was to completely decouple RMP updates for physical pages from the KVM MMU map/unmap paths since the life-cycles of those backing pages and associated RMP state are somewhat separate from the state of the GFNs and kvm->mem_attr_array. It seems to make sense when dealing with things like this unbind() case.
There's also cases like userspaces that opt to not discard memory after conversions because they highly favor performance over memory usage. In those cases it would make sense to defer marking the pages as shared in the RMP until the FALLOC_FL_PUNCH_HOLE, rather than triggering it via KVM MMU invalidation path after a conversion.
Quoted 6 lines
Quoted 2 lines
KVM: x86: Add platform hook for initializing private memory
This should also be unnecessary. The call to
kvm_gmem_get_pfn()is from arch code, KVM just needs to ensure the RMP is converted before acquiringmmu_lock, e.g. KVM has all the necessary info inkvm_tdp_mmu_page_fault().
I think that approach would work fine. The way I was thinking of things is that KVM MMU would necessarily call kvm_gmem_get_pfn() to grab the page before mapping it into the guest, so moving it out into an explicit call should work just as well. That would also drop the need for the __kvm_gmem_get_pfn() stuff I needed to add for the initial case where we need to access the PFN prior to making it private.
Quoted 7 lines
The only reason to add another arch hook would be if we wanted to converted the RMP when _allocating_, e.g. to preconvert in response to
fallocate()instead of waiting until #NPF. But I think I would rather add a genericioctl()to allow userspace to effectively prefault guest memory, e.g. to setup the RMP before running a vCPU. Such anioctl()would potentially be useful in other scenarios, e.g. on the dest during live migration to reduce jitter.
Agreed, deferring the RMPUPDATE until it's actually needed would give us more flexibility on optimizing for things like lazy-acceptance.
For less-common scenarios like preallocation it makes sense to make that an opt-in sort of thing for userspace to configure explicitly.
Quoted 9 lines
Quoted 2 lines
*fixup (
kvm_gmem_solo): KVM: Fix end range calculation for MMU invalidationsThere was another bug in this path. The math for handling a non-zero offsets into the file was wrong. The code now looks like:
xa_for_each_range(&gmem->bindings, index, slot, start, end - 1) { struct kvm_gfn_range gfn_range = { .start = slot->base_gfn + start - slot->gmem.index,
Sorry if I'm missing something here, but isn't there a risk that:
start - slot->gmem.index
would be less than zero? E.g. starting GFN was 0, but current slot is bound at some non-zero offset in the same gmem instance. I guess the warning below shouldn't caught that, but it seems like a real scenario.
Since 'index' corresponds to the gmem offset of the current slot, is there any reason not to do something like this?:
.start = slot->base_gfn + index - slot->gmem.index,
But then, if that's the case, wouldn't index == slot->gmem.index? Suggesting we case just simplify to this?:
.start = slot->base_gfn,
-Mike
Quoted 14 lines
.end = slot->base_gfn + min(end - slot->gmem.index, slot->npages), .slot = slot, .pte = __pte(0), .may_block = true, }; if (WARN_ON_ONCE(start < slot->gmem.index || end > slot->gmem.index + slot->npages)) continue; kvm_mmu_invalidate_range_add(kvm, gfn_range.start, gfn_range.end); flush |= kvm_unmap_gfn_range(kvm, &gfn_range); }
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 15 lines · Michael Roth
Quoted 14 lines · Sean Christopherson
Quoted 12 lines · Michael Roth
Quoted 1 line · Sean Christopherson
On Thu, May 11, 2023, Michael Roth wrote:
I put together a tree with some fixups that are needed for against the
kvm_gmem_solobase tree, and a set of hooks to handle invalidations, preparing the initial private state as suggested above, and a platform-configurable mask that the x86 MMU code can use for determining whether a fault is for private vs. shared pages.KVM: x86: Determine shared/private faults using a configurable mask ^ for TDX we could trivially add an inverted analogue of the mask/logic
KVM: x86: Use full 64-bit error code forkvm_mmu_do_page_fault
KVM: x86: Add platform hooks for private memory invalidations
Quoted 7 lines
Quoted 4 lines
Hrm, I'd prefer to avoid adding another hook for this case, arch code already has a "hook" in the form of
kvm_unmap_gfn_range(). We'd probably just need a kvm_gfn_range.is_private flag to communicate toarch/vendorcode that the memory being zapped is private.
kvm_unmap_gfn_range()does however get called withkvm->mmu_lockheld so it might be tricky to tie RMP updates into that path.
Gah, I caught the mmu_lock issue before the end of my email, but forgot to go back and rethink the first half.
Quoted 5 lines
Quoted 5 lines
That'd leave a gap for the
unbind()case becausekvm_unmap_gfn_range()is invoked if and only if there's an overlapping memslot. I'll chew on that a bit to see if there's a way to cleanly handle that case without another hook. I think it's worth mapping out exactly what we wantunbind()to look like anyways, e.g. right now the code subtly relies on private memslots being immutable.
m
Quoted 11 lines
I thought the direction you sort of driving at was to completely decouple RMP updates for physical pages from the KVM MMU map/unmap paths since the life-cycles of those backing pages and associated RMP state are somewhat separate from the state of the GFNs and
kvm->mem_attr_array. It seems to make sense when dealing with things like thisunbind()case.There's also cases like userspaces that opt to not discard memory after conversions because they highly favor performance over memory usage. In those cases it would make sense to defer marking the pages as shared in the RMP until the
FALLOC_FL_PUNCH_HOLE, rather than triggering it via KVM MMU invalidation path after a conversion.
Hmm, right. I got overzealous in my desire to avoid new hooks.
Quoted 37 lines
Quoted 5 lines
Quoted 1 line
KVM: x86: Add platform hook for initializing private memory
This should also be unnecessary. The call to
kvm_gmem_get_pfn()is from arch code, KVM just needs to ensure the RMP is converted before acquiringmmu_lock, e.g. KVM has all the necessary info inkvm_tdp_mmu_page_fault().I think that approach would work fine. The way I was thinking of things is that KVM MMU would necessarily call
kvm_gmem_get_pfn()to grab the page before mapping it into the guest, so moving it out into an explicit call should work just as well. That would also drop the need for the__kvm_gmem_get_pfn()stuff I needed to add for the initial case where we need to access the PFN prior to making it private.Quoted 7 lines
The only reason to add another arch hook would be if we wanted to converted the RMP when _allocating_, e.g. to preconvert in response to
fallocate()instead of waiting until #NPF. But I think I would rather add a genericioctl()to allow userspace to effectively prefault guest memory, e.g. to setup the RMP before running a vCPU. Such anioctl()would potentially be useful in other scenarios, e.g. on the dest during live migration to reduce jitter.Agreed, deferring the RMPUPDATE until it's actually needed would give us more flexibility on optimizing for things like lazy-acceptance.
For less-common scenarios like preallocation it makes sense to make that an opt-in sort of thing for userspace to configure explicitly.
Quoted 9 lines
Quoted 2 lines
*fixup (
kvm_gmem_solo): KVM: Fix end range calculation for MMU invalidationsThere was another bug in this path. The math for handling a non-zero offsets into the file was wrong. The code now looks like:
xa_for_each_range(&gmem->bindings, index, slot, start, end - 1) { struct kvm_gfn_range gfn_range = { .start = slot->base_gfn + start - slot->gmem.index,
Quoted 7 lines
Sorry if I'm missing something here, but isn't there a risk that:
start -
slot->gmem.indexwould be less than zero? E.g. starting GFN was 0, but current slot is bound at some non-zero offset in the same gmem instance. I guess the warning below shouldn't caught that, but it seems like a real scenario.
Heh, only if there's a testcase for it. Assuming start >= the slot offset does seem broken, e.g. if the range-to-invalidate overlaps multiple slots, later slots will have index==slot->gmem.index > start.
Quoted 9 lines
Since 'index' corresponds to the gmem offset of the current slot, is there any reason not to do something like this?:
.start =
slot->base_gfn+ index -slot->gmem.index,But then, if that's the case, wouldn't index ==
slot->gmem.index? Suggesting we case just simplify to this?:.start =
slot->base_gfn,
No, e.g. if start is partway through a memslot, there's no need to invalidate the entire memslot. I'll stare at this tomorrow when my brain is hopefully a bit more functional, I suspect there is a min() and/or max() needed somewhere.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonI've ported selftests from Chao and I [1] while working on hugetlb support for guest_mem [2].
In the process, I found some bugs and have some suggestions for guest_mem. Please see separate commits at [3].
Here are some highlights/questions:
+ "KVM: guest_mem: Explain the use of the uptodate flag for gmem"
+ Generally, uptodate flags means that the contents of this page match
the
backing store. Since gmem is memory-backed, does "uptodate" for gmem
mean
"zeroed"?
+ "KVM: guest_mem: Don't re-mark accessed after getting a folio" and "KVM:
guest_mem: Don't set dirty flag for folio"
+ Do we need to folio_mark_accessed(), when it was created with FGP_ACCESSED?
+ What is the significance of these LRU flags when gmem doesn't support swapping/eviction?
+ "KVM: guest_mem: Align so that at least 1 page is allocated"
+ Bug in current implementation: without this alignment, fallocate() of
a
size less than the gmem page size will result in no allocation at all
+ Both shmem and hugetlbfs perform this alignment
+ "KVM: guest_mem: Add alignment checks"
+ Implemented the alignment checks for guest_mem because hugetlb on gmem would hit a BUG_ON without this check
+ "KVM: guest_mem: Prevent overflows in kvm_gmem_invalidate_begin()"
+ Sean fixed a bug in the offset-to-gfn conversion in kvm_gmem_invalidate_begin() earlier, adding a WARN_ON_ONCE()
+ Code will always hit WARN_ON_ONCE() when the entire file is closed
and all
offsets are invalidated, so WARN_ON_ONCE() should be removed
+ Vishal noticed that the conversion might result in an overflow, so I
fixed
that
+ And of course, hugetlb support! Please let me know what you think of the approach proposed at [2].
[1] https://lore.kernel.org/all/[email protected]/T/
[2] https://lore.kernel.org/lkml/[email protected]/T/
[3] https://github.com/googleprodkernel/linux-cc/tree/gmem-hugetlb-rfc-v1
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 7 lines · Ackerley Tng
I've ported selftests from Chao and I [1] while working on hugetlb support for
guest_mem[2].In the process, I found some bugs and have some suggestions for
guest_mem. Please see separate commits at [3].
Quoted 6 lines
Here are some highlights/questions:
+ "KVM:
guest_mem: Explain the use of the uptodate flag for gmem"
+ Generally, uptodate flags means that the contents of this page match the backing store. Since gmem is memory-backed, does "uptodate" for gmem mean "zeroed"?
Don't read too much into the code, my POC was very much a "beat on it until it works" scenario.
Quoted 4 lines
+ "KVM:
guest_mem: Don't re-mark accessed after getting a folio" and "KVM:
guest_mem: Don't set dirty flag for folio"
+ Do we need tofolio_mark_accessed(), when it was created withFGP_ACCESSED?
Probably not. And as you note below, it's all pretty nonsensical anyways.
Quoted 2 lines
+ What is the significance of these LRU flags when gmem doesn't support swapping/eviction?
Likely none. I used the filemap APIs in my POC because it was easy, not because it was necessarily the best approach, i.e. that the folios/pages show up in the LRUs is an unwanted side effect, not a feature. If guest_memfd only needs a small subset of the filemap support, going with a true from-scratch implemenation on top of xarray might be cleaner overall, e.g. would avoid the need for a new flag to say "this folio can't be migrated even though it's on the LRUs".
Quoted 3 lines
+ "KVM:
guest_mem: Align so that at least 1 page is allocated"
+ Bug in current implementation: without this alignment,fallocate()of a size less than the gmem page size will result in no allocation at all
I'm not convinced this is a bug. I don't see any reason to allow allocating and punching holes in sub-page granularity.
Quoted 7 lines
+ Both shmem and hugetlbfs perform this alignment
+ "KVM:guest_mem: Add alignment checks"
+ Implemented the alignment checks forguest_membecause hugetlb on gmem would hit aBUG_ONwithout this check
+ "KVM:guest_mem: Prevent overflows inkvm_gmem_invalidate_begin()"
+ Sean fixed a bug in the offset-to-gfn conversion inkvm_gmem_invalidate_begin()earlier, adding aWARN_ON_ONCE()
As Mike pointed out, there's likely still a bug here[*]. I was planning on diving into that last week, but that never happened. If you or anyone else can take a peek and/or write a testcase, that would be awesome.
: Heh, only if there's a testcase for it. Assuming start >= the slot offset does : seem broken, e.g. if the range-to-invalidate overlaps multiple slots, later slots : will have index==slot->gmem.index > start.
:
: > Since 'index' corresponds to the gmem offset of the current slot, is there any : > reason not to do something like this?:
: >
: > .start = slot->base_gfn + index - slot->gmem.index,
: >
: > But then, if that's the case, wouldn't index == slot->gmem.index? Suggesting : > we case just simplify to this?:
: >
: > .start = slot->base_gfn,
:
: No, e.g. if start is partway through a memslot, there's no need to invalidate : the entire memslot. I'll stare at this tomorrow when my brain is hopefully a : bit more functional, I suspect there is a min() and/or max() needed somewhere.
[*] https://lore.kernel.org/all/[email protected]
Quoted 10 lines
+ Code will always hit
WARN_ON_ONCE()when the entire file is closed and all offsets are invalidated, soWARN_ON_ONCE()should be removed
+ Vishal noticed that the conversion might result in an overflow, so I fixed that
+ And of course, hugetlb support! Please let me know what you think of the approach proposed at [2].[1] https://lore.kernel.org/all/[email protected]/T/
[2] https://lore.kernel.org/lkml/[email protected]/T/
[3] https://github.com/googleprodkernel/linux-cc/tree/gmem-hugetlb-rfc-v1
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 8 lines · Sean Christopherson
...
Probably not. And as you note below, it's all pretty nonsensical anyways.
Quoted 3 lines
+ What is the significance of these LRU flags when gmem doesn't
support
swapping/eviction?
Quoted 6 lines
Likely none. I used the filemap APIs in my POC because it was easy, not because it was necessarily the best approach, i.e. that the folios/pages show up in the LRUs is an unwanted side effect, not a feature. If
guest_memfdonly needs a small subset of the filemap support, going with a true from-scratch implemenation on top of xarray might be cleaner overall, e.g. would avoid the need for a new flag to say "this folio can't be migrated even though it's on the LRUs".
For hugetlb support on gmem, using an xarray in place of a filemap should work fine. Page migration could come up in future - perhaps migration code works better with filemap? Not sure.
Quoted 8 lines
Quoted 5 lines
+ "KVM:
guest_mem: Align so that at least 1 page is allocated"
+ Bug in current implementation: without this alignment,fallocate()
of
a size less than the gmem page size will result in no allocation
at allI'm not convinced this is a bug. I don't see any reason to allow allocating and punching holes in sub-page granularity.
I looked at the code more closely, you're right. len is checked to be 4K aligned. When userspace requests a gmem page size of larger than 4K (gmem THP), the allocation loop still does the right thing.
This issue only arises for hugetlb pages. I'll rebase the next revision of the hugetlb series accordingly.
Quoted 38 lines
Quoted 8 lines
+ Both shmem and hugetlbfs perform this alignment
+ "KVM:guest_mem: Add alignment checks"
+ Implemented the alignment checks forguest_membecause hugetlb on
gmem
would hit aBUG_ONwithout this check
+ "KVM:guest_mem: Prevent overflows inkvm_gmem_invalidate_begin()"
+ Sean fixed a bug in the offset-to-gfn conversion inkvm_gmem_invalidate_begin()earlier, adding aWARN_ON_ONCE()As Mike pointed out, there's likely still a bug here[*]. I was planning on diving into that last week, but that never happened. If you or anyone else can take a peek and/or write a testcase, that would be awesome.
: Heh, only if there's a testcase for it. Assuming start >= the slot
offset does
: seem broken, e.g. if the range-to-invalidate overlaps multiple slots,
later slots
: will have index==slot->gmem.index > start.
:
: > Since 'index' corresponds to the gmem offset of the current slot, is
there any
: > reason not to do something like this?:
: >
: > .start =slot->base_gfn+ index -slot->gmem.index,
: >
: > But then, if that's the case, wouldn't index ==slot->gmem.index?
Suggesting
: > we case just simplify to this?:
: >
: > .start =slot->base_gfn,
:
: No, e.g. if start is partway through a memslot, there's no need to
invalidate
: the entire memslot. I'll stare at this tomorrow when my brain is
hopefully a
: bit more functional, I suspect there is amin()and/ormax()needed
somewhere.
Quoted 1 line
I think I have fixed this, please see "KVM: guest_mem: Prevent overflows in kvm_gmem_invalidate_begin()" [1].
This patch does take into account that start could be greater than slot->gmem.index, when userspace chooses to punch holes beginning in the middle of the memslot.
The process could be split into figuring out file indices, then GFNs:
1. Figure out the start and end in terms of index in the file
+ index_start: taking max(start, slot->gmem.index)
+ start will only be greater than slot->gmem.index but not greater
than
the end of the slot, due to the nature of xa_for_each_range()
+ index_end: taking min(end, slot->gmem.index + slot->npages)
2. Convert indices to GFNs
This also prevents overflows as described at [1].
Quoted 1 line
Quoted 1 line
...
[1] https://github.com/googleprodkernel/linux-cc/commit/bcc304e3657a998b8f61aa1b841754fbb90d8994
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 18 lines · Sean Christopherson
Quoted 9 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
I want to start referring to the code/patches by its
syscall/implementation name
Quoted 1 line
instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the
broader effort
Quoted 1 line
and not just the non-KVM code, and (c) will likely be confusing for
future reviewers
Quoted 2 lines
since there's nothing in the code that mentions "UPM" in any way.
Quoted 7 lines
Quoted 1 line
But typing out restrictedmem is quite tedious, and git grep shows
that "rmem" is
Quoted 3 lines
already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
Quoted 7 lines
I'm definitely open to other suggestions, but I suspect it's going to be difficult to be more precise than something like "guarded".
E.g. we discussed "unmappable" at one point, but the memory can still be mapped, just not via
mmap(). And it's not just about mappings, e.g.read()and its many variants are all disallowed too, despite the kernel direct map still being live (modulo SNP requirements).
I'm for renaming the concept because restrictedmem is quite a mouthful. :)
How about "concealedmem" or "obscuredmem" to highlight the idea of this memory being hidden/unreadable/unmappable from userspace?
Guarded is better than restricted but doesn't really highlight how/in what way it is being guarded.
Sean Christopherson
maintainerRe: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 6 lines · Ackerley Tng
Quoted 4 lines · Sean Christopherson
Quoted 3 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
I want to start referring to the code/patches by its
syscall/implementation name
Quoted 8 lines
Quoted 8 lines
Quoted 8 lines
Quoted 1 line
instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to
the broader effort
Quoted 1 line
and not just the non-KVM code, and (c) will likely be confusing
for future reviewers
Quoted 3 lines
since there's nothing in the code that mentions "UPM" in any way.
But typing out restrictedmem is quite tedious, and git grep shows
that "rmem" is
Your mail client appears to be wrapping too aggressively and mangling quotes. I'm guessing gmail is to blame?
Quoted 19 lines
Quoted 11 lines
Quoted 6 lines
Quoted 3 lines
already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
I'm definitely open to other suggestions, but I suspect it's going to be difficult
to be more precise than something like "guarded".Quoted 7 lines
E.g. we discussed "unmappable" at one point, but the memory can still be mapped,
just not viammap(). And it's not just about mappings, e.g.read()and its many
variants are all disallowed too, despite the kernel direct map still being live
(modulo SNP requirements).
Quoted 5 lines
I'm for renaming the concept because restrictedmem is quite a mouthful. :)
How about "concealedmem" or "obscuredmem" to highlight the idea of this memory being hidden/unreadable/unmappable from userspace?
I'm hesitant to use something like "concealed" becuase it's too close to secretmem, e.g. might be miscontrued as concealing the memory from anything _but_ the process that creates the file.
Obscured has similar problems, and obscure often suggests that something is unclear, as opposed to outright unreachable.
The other aspect of hidden/concealed/etc is that the memory isn't necessarily concealed from the user. Long term, I hope to get to the point where even "normal" VMs use restricted/guarded/??? memory, e.g. to guard (heh) against _unintentional_ access from userspace. In that use case, the memory isn't truly concealed, espeically if the user is both the "admin" and the consumer.
Though by that argument, "guarded" is also a poor choice. And now I'm remembering how we ended up with "restricted"...
Quoted 2 lines
Guarded is better than restricted but doesn't really highlight how/in what way it is being guarded.
Ya, though in practice I think it's infeasible for us to get a name that is both precise and succinct.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 18 lines · Sean Christopherson
Quoted 9 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
I want to start referring to the code/patches by its
syscall/implementation name
Quoted 1 line
instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the
broader effort
Quoted 1 line
and not just the non-KVM code, and (c) will likely be confusing for
future reviewers
Quoted 2 lines
since there's nothing in the code that mentions "UPM" in any way.
Quoted 7 lines
Quoted 1 line
But typing out restrictedmem is quite tedious, and git grep shows
that "rmem" is
Quoted 3 lines
already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
Quoted 7 lines
I'm definitely open to other suggestions, but I suspect it's going to be difficult to be more precise than something like "guarded".
E.g. we discussed "unmappable" at one point, but the memory can still be mapped, just not via
mmap(). And it's not just about mappings, e.g.read()and its many variants are all disallowed too, despite the kernel direct map still being live (modulo SNP requirements).
How about "opaque"?
I think opaque captures the idea of enforced information hiding from the user(space), and that the contents can only be manipulated via internal (kernel) functions.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to David HildenbrandQuoted 14 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
What do y'all think about renaming "restrictedmem" to "guardedmem"?
Yeay, let's add more confusion :D
If we're at renaming, I'd appreciate if we could find a terminology that does look/sound less horrible.
Quoted 6 lines
I want to start referring to the code/patches by its syscall/implementation name instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the broader effort and not just the non-KVM code, and (c) will likely be confusing for future reviewers since there's nothing in the code that mentions "UPM" in any way.
Quoted 6 lines
Quoted 4 lines
But typing out restrictedmem is quite tedious, and git grep shows that "rmem" is already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
In the world of TEE's and confidential computing it is fairly common to call memory areas enclaves, even outside SGX context. So in that sense enclave memory would be the most correct terminology.
BR, Jarkko
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 18 lines · Jarkko Sakkinen
Quoted 17 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
What do y'all think about renaming "restrictedmem" to "guardedmem"?
Yeay, let's add more confusion :D
If we're at renaming, I'd appreciate if we could find a terminology that does look/sound less horrible.
Quoted 9 lines
I want to start referring to the code/patches by its syscall/implementation name instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the broader effort and not just the non-KVM code, and (c) will likely be confusing for future reviewers since there's nothing in the code that mentions "UPM" in any way.
But typing out restrictedmem is quite tedious, and git grep shows that "rmem" is already used to refer to "reserved memory".
Quoted 7 lines
Quoted 3 lines
Quoted 1 line
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
In the world of TEE's and confidential computing it is fairly common to call memory areas enclaves, even outside SGX context. So in that sense enclave memory would be the most correct terminology.
I was also thinking along the lines of isolated_mem or imem ... essentially, isolated from (unprivileged) user space.
... if we still want to have a common syscall for it.
Signature · 3 lines
Thanks, David / dhildenb
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 23 lines · David Hildenbrand
Quoted 22 lines · Jarkko Sakkinen
Quoted 20 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
What do y'all think about renaming "restrictedmem" to "guardedmem"?
Yeay, let's add more confusion :D
If we're at renaming, I'd appreciate if we could find a terminology that does look/sound less horrible.
Quoted 10 lines
I want to start referring to the code/patches by its syscall/implementation name instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the broader effort and not just the non-KVM code, and (c) will likely be confusing for future reviewers since there's nothing in the code that mentions "UPM" in any way.
But typing out restrictedmem is quite tedious, and git grep shows that "rmem" is already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
Quoted 8 lines
Quoted 3 lines
In the world of TEE's and confidential computing it is fairly common to call memory areas enclaves, even outside SGX context. So in that sense enclave memory would be the most correct terminology.
I was also thinking along the lines of
isolated_memor imem ... essentially, isolated from (unprivileged) user space.... if we still want to have a common syscall for it.
I'm fan of the ioctl, if it has a chance of working out.
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
Quoted 28 lines · Vlastimil Babka
Quoted 27 lines · David Hildenbrand
Quoted 25 lines · Jarkko Sakkinen
Quoted 14 lines · David Hildenbrand
Quoted 1 line · Sean Christopherson
What do y'all think about renaming "restrictedmem" to "guardedmem"?
Yeay, let's add more confusion :D
If we're at renaming, I'd appreciate if we could find a terminology that does look/sound less horrible.
Quoted 6 lines
I want to start referring to the code/patches by its syscall/implementation name instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the broader effort and not just the non-KVM code, and (c) will likely be confusing for future reviewers since there's nothing in the code that mentions "UPM" in any way.
Quoted 6 lines
Quoted 4 lines
But typing out restrictedmem is quite tedious, and git grep shows that "rmem" is already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
restrictedmem, guardedmem, ... all fairly "suboptimal" if you'd ask me ...
In the world of TEE's and confidential computing it is fairly common to call memory areas enclaves, even outside SGX context. So in that sense enclave memory would be the most correct terminology.
Quoted 6 lines
Quoted 4 lines
I was also thinking along the lines of
isolated_memor imem ... essentially, isolated from (unprivileged) user space.... if we still want to have a common syscall for it.
I'm fan of the ioctl, if it has a chance of working out.
Yes, me too.
Signature · 3 lines
Thanks, David / dhildenb
Re: Rename restrictedmem => guardedmem? (was: Re: [PATCH v10 0/9] KVM: mm: fd-based approach for supporting KVM)
In reply to Sean ChristophersonQuoted 23 lines · Sean Christopherson
What do y'all think about renaming "restrictedmem" to "guardedmem"?
I want to start referring to the code/patches by its syscall/implementation name instead of "UPM", as "UPM" is (a) very KVM centric, (b) refers to the broader effort and not just the non-KVM code, and (c) will likely be confusing for future reviewers since there's nothing in the code that mentions "UPM" in any way.
But typing out restrictedmem is quite tedious, and git grep shows that "rmem" is already used to refer to "reserved memory".
Renaming the syscall to "guardedmem"...
1. Allows for a shorthand and namespace, "gmem", that isn't already in use by the kernel (see "reserved memory above").
2. Provides a stronger hint as to its purpose. "Restricted" conveys that the allocated memory is limited in some way, but doesn't capture how the memory is restricted, e.g. "restricted" could just as easily mean that the allocation can be restricted to certain types of backing stores or something. "Guarded" on the other hand captures that the memory has extra defenses of some form.
3. Is shorter to type and speak. Work smart, not hard :-)
Quoted 7 lines
4. Isn't totally wrong for the KVM use case if someone assumes the "g" means "guest" when reading mail and whatnot.
P.S. I trimmed the Cc/To substantially for this particular discussion to avoid
spamming folks that don't (yet) care about this stuff with another potentially lengthy thread. Feel free to add (back) any people/lists.
I guess 'guarded' could be a good noun in the sense that it does not get easily mixed up to anything pre-existing, and it does give the idea of the purpose.
BR, Jarkko
v3 cover Providing mount in memfd_restricted() syscall
Hello,
This patchset builds upon the memfd_restricted() system call that was discussed in the ‘KVM: mm: fd-based approach for supporting KVM’ patch series, at
https://lore.kernel.org/lkml/[email protected]/T/
The tree can be found at:
https://github.com/googleprodkernel/linux-cc/tree/restrictedmem-provide-mount-fd-rfc-v3
In this patchset, a modification to the memfd_restricted() syscall is proposed, which allows userspace to provide a mount, on which the restrictedmem file will be created and returned from the memfd_restricted().
Allowing userspace to provide a mount allows userspace to control various memory binding policies via tmpfs mount options, such as Transparent HugePage memory allocation policy through ‘huge=always/never’ and NUMA memory allocation policy through ‘mpol=local/bind:*’.
Changes since RFCv2:
+ Tightened semantics to accept only fds of the root of a tmpfs mount, as Christian suggested
+ Added permissions check on the inode represented by the fd to guard against creation of restrictedmem files on read-only tmpfs filesystems or mounts
+ Renamed RMFD_TMPFILE to RMFD_USERMNT to better represent providing a userspace mount to create a restrictedmem file on
+ Updated selftests for tighter semantics and added selftests to check for permissions
Changes since RFCv1:
+ Use fd to represent mount instead of path string, as Kirill suggested. I believe using fds makes this syscall interface more aligned with the other syscalls like fsopen(), fsconfig(), and fsmount() in terms of using and passing around fds
+ Remove unused variable char *orig_shmem_enabled from selftests
Dependencies:
+ Sean’s iteration of the ‘KVM: mm: fd-based approach for supporting
KVM’ patch series at
https://github.com/sean-jc/linux/tree/x86/upm_base_support
+ Proposed fixes for these issues mentioned on the mailing list:
+ https://lore.kernel.org/lkml/[email protected]/
Links to earlier patch series:
+ RFC v2: https://lore.kernel.org/lkml/[email protected]/T/
+ RFC v1: https://lore.kernel.org/lkml/[email protected]/T/
---
Ackerley Tng (2):
mm: restrictedmem: Allow userspace to specify mount for
memfd_restricted
selftests: restrictedmem: Check hugepage-ness of shmem file backing
restrictedmem fd
include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 + mm/restrictedmem.c | 74 ++- tools/testing/selftests/Makefile | 1 + .../selftests/restrictedmem/.gitignore | 3 + .../testing/selftests/restrictedmem/Makefile | 15 + .../testing/selftests/restrictedmem/common.c | 9 + .../testing/selftests/restrictedmem/common.h | 8 + .../restrictedmem_hugepage_test.c | 486 ++++++++++++++++++ 9 files changed, 599 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/restrictedmem.h create mode 100644 tools/testing/selftests/restrictedmem/.gitignore create mode 100644 tools/testing/selftests/restrictedmem/Makefile create mode 100644 tools/testing/selftests/restrictedmem/common.c create mode 100644 tools/testing/selftests/restrictedmem/common.h create mode 100644 tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c
--
2.40.0.348.gf938b09366-goog
RFC PATCH v3 1/2 mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
By default, the backing shmem file for a restrictedmem fd is created on shmem's kernel space mount.
With this patch, an optional tmpfs mount can be specified via an fd, which will be used as the mountpoint for backing the shmem file associated with a restrictedmem fd.
This will help restrictedmem fds inherit the properties of the provided tmpfs mounts, for example, hugepage allocation hints, NUMA binding hints, etc.
Permissions for the fd passed to memfd_restricted() is modeled after the openat() syscall, since both of these allow creation of a file upon a mount/directory.
Permission to reference the mount the fd represents is checked upon fd creation by other syscalls (e.g. fsmount(), open(), or open_tree(), etc) and any process that can present memfd_restricted() with a valid fd is expected to have obtained permission to use the mount represented by the fd. This behavior is intended to parallel that of the openat() syscall.
memfd_restricted() will check that the tmpfs superblock is writable, and that the mount is also writable, before attempting to create a restrictedmem file on the mount.
--- include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 ++++ mm/restrictedmem.c | 74 +++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/restrictedmem.h
include/linux/syscalls.h +1 -1
@@ -1056,7 +1056,7 @@ asmlinkage long sys_memfd_secret(unsigned int flags);asmlinkage long sys_set_mempolicy_home_node (unsigned long start,unsigned long len,unsigned long home_node,unsigned long flags); -asmlinkage long sys_memfd_restricted (unsigned int flags); +asmlinkage long sys_memfd_restricted (unsigned int flags,int mount_fd);/* * Architecture-specific system calls
include/uapi/linux/restrictedmem.h new file +8 -0
@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_USERMNT 0x0001U + +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */
mm/restrictedmem.c +68 -6
104 lines not on this page · raw message
--
2.40.0.348.gf938b09366-goog
Re: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Quoted 53 lines · Ackerley Tng
By default, the backing shmem file for a restrictedmem fd is created on shmem's kernel space mount.
With this patch, an optional tmpfs mount can be specified via an fd, which will be used as the mountpoint for backing the shmem file associated with a restrictedmem fd.
This will help restrictedmem fds inherit the properties of the provided tmpfs mounts, for example, hugepage allocation hints, NUMA binding hints, etc.
Permissions for the fd passed to
memfd_restricted()is modeled after theopenat()syscall, since both of these allow creation of a file upon a mount/directory.Permission to reference the mount the fd represents is checked upon fd creation by other syscalls (e.g.
fsmount(),open(), oropen_tree(), etc) and any process that can presentmemfd_restricted()with a valid fd is expected to have obtained permission to use the mount represented by the fd. This behavior is intended to parallel that of theopenat()syscall.
memfd_restricted()will check that the tmpfs superblock is writable, and that the mount is also writable, before attempting to create a restrictedmem file on the mount.Signed-off-by Ackerley Tng <[email protected]>--- include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 ++++ mm/restrictedmem.c | 74 +++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-)create mode 100644
include/uapi/linux/restrictedmem.hinclude/linux/syscalls.h +1
-1@@ -1056,7 +1056,7 @@ asmlinkage long sys_memfd_secret(unsigned int flags);asmlinkage long sys_set_mempolicy_home_node (unsigned long start,unsigned long len,unsigned long home_node,unsigned long flags); -asmlinkage long sys_memfd_restricted (unsigned int flags); +asmlinkage long sys_memfd_restricted (unsigned int flags,int mount_fd);/* * Architecture-specific system calls include/uapi/linux/restrictedmem.h new file +0
-0
include/uapi/linux/restrictedmem.h line 6
Quoted 6 lines
include/uapi/linux/restrictedmem.h new file +6
-0@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_USERMNT 0x0001U
I wonder if we can come up with a more expressive prefix than RMFD. Sounds more like "rm fd" ;) Maybe it should better match the "memfd_restricted" syscall name, like "MEMFD_RSTD_USERMNT".
mm/restrictedmem.c line 2
Quoted 9 lines
include/uapi/linux/restrictedmem.h new file +2
-0+ +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */ mm/restrictedmem.c +0
-1@@ -1,11 +1,12 @@// SPDX-License-Identifier: GPL-2.0 -#include "linux/sbitmap.h"
Looks like an unrelated change?
Quoted 40 lines
mm/restrictedmem.c +8
-5+#include <linux/namei.h> #include <linux/pagemap.h> #include <linux/pseudo_fs.h> #include <linux/shmem_fs.h> #include <linux/syscalls.h> #include <uapi/linux/falloc.h> #include <uapi/linux/magic.h> +#include <uapi/linux/restrictedmem.h> #include <linux/restrictedmem.h> struct restrictedmem {@@ -189,19 +190,20 @@ static struct file *restrictedmem_file_create(struct file *memfd)return file; } -SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err; -if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file =shmem_file_setup_with_mnt (mount,"memfd:restrictedmem" ,0 ,VM_NORESERVE ); +else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) { err =PTR_ERR (file); goto err_fd;@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags)return err; }
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 236
Quoted 10 lines
mm/restrictedmem.c +9
-0+static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ; +} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root; +}
I'd inline at least that function, pretty self-explaining.
Quoted 39 lines
mm/restrictedmem.c +39
-0+ +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ; + + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt)) + goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC ); +if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret)) + goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f); + +return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ +if (flags & ~RMFD_USERMNT )
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 285
Quoted 10 lines
mm/restrictedmem.c +10
-0+return -EINVAL ; + +if (flags ==RMFD_USERMNT ) { +if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + }
You can drop the else case:
if (flags == RMFD_USERMNT) {
...
return restrictedmem_create_on_user_mount(mount_fd);
}return restrictedmem_create(NULL);
I do wonder if you want to properly check for a flag instead of comparing values. Results in a more natural way to deal with flags:
if (flags & RMFD_USERMNT) {
}
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 290
Quoted 5 lines
mm/restrictedmem.c +2
-0+} +int restrictedmem_bind (struct file *file,pgoff_t start,pgoff_t end,struct restrictedmem_notifier *notifier,bool exclusive) {
The "memfd_restricted" vs. "restrictedmem" terminology is a bit unfortunate, but not your fault here.
I'm not a FS person, but it does look good to me.
Signature · 3 lines
Thanks, David / dhildenb
Re: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Thanks for your review!
Quoted 4 lines · David Hildenbrand
Quoted 1 line · Ackerley Tng
...
Quoted 16 lines
include/uapi/linux/restrictedmem.h line 6
Quoted 12 lines
include/uapi/linux/restrictedmem.h new file +6
-0@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_USERMNT 0x0001UI wonder if we can come up with a more expressive prefix than RMFD. Sounds more like "rm fd" ;) Maybe it should better match the "
memfd_restricted" syscall name, like "MEMFD_RSTD_USERMNT".
RMFD did actually sound vulgar, I'm good with MEMFD_RSTD_USERMNT!
Quoted 11 lines
mm/restrictedmem.c line 2
Quoted 9 lines
include/uapi/linux/restrictedmem.h new file +2
-0+ +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */ mm/restrictedmem.c +0
-1@@ -1,11 +1,12 @@// SPDX-License-Identifier: GPL-2.0 -#include "linux/sbitmap.h" Looks like an unrelated change?
Will remove this in the next revision.
Quoted 44 lines
Quoted 43 lines
mm/restrictedmem.c +8
-5+#include <linux/namei.h> #include <linux/pagemap.h> #include <linux/pseudo_fs.h> #include <linux/shmem_fs.h> #include <linux/syscalls.h> #include <uapi/linux/falloc.h> #include <uapi/linux/magic.h> +#include <uapi/linux/restrictedmem.h> #include <linux/restrictedmem.h> struct restrictedmem {@@ -189,19 +190,20 @@ static struct file *restrictedmem_file_create(struct file *memfd)return file; } -SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err; -if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file =shmem_file_setup_with_mnt (mount,"memfd:restrictedmem" ,0 ,VM_NORESERVE ); +else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) {err = PTR_ERR(file); goto err_fd;mm/restrictedmem.c +0
-0@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags)return err; }
Quoted 11 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 236
Quoted 9 lines
mm/restrictedmem.c +9
-0+static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ; +} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root; +}I'd inline at least that function, pretty self-explaining.
Will inline this in the next revision.
Quoted 53 lines
Quoted 39 lines
mm/restrictedmem.c +39
-0+ +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ; + + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt)) + goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC ); +if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret)) + goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f); + +return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ +if (flags & ~RMFD_USERMNT )mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 285
Quoted 10 lines
mm/restrictedmem.c +10
-0+return -EINVAL ; + +if (flags ==RMFD_USERMNT ) { +if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + }You can drop the else case:
Quoted 5 lines
if (flags == RMFD_USERMNT) { ... return restrictedmem_create_on_user_mount(mount_fd); }return
restrictedmem_create(NULL);
I'll be refactoring this to adopt Kirill's suggestion of using a single restrictedmem_create(mnt) call.
Quoted 6 lines
I do wonder if you want to properly check for a flag instead of comparing values. Results in a more natural way to deal with flags:
if (flags &
RMFD_USERMNT) {}
Will use this in the next revision.
Quoted 11 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 290
Quoted 5 lines
mm/restrictedmem.c +2
-0+} +int restrictedmem_bind (struct file *file,pgoff_t start,pgoff_t end,struct restrictedmem_notifier *notifier,bool exclusive) {The "
memfd_restricted" vs. "restrictedmem" terminology is a bit unfortunate, but not your fault here.I'm not a FS person, but it does look good to me.
Re: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
In reply to Ackerley TngQuoted 128 lines · Ackerley Tng
By default, the backing shmem file for a restrictedmem fd is created on shmem's kernel space mount.
With this patch, an optional tmpfs mount can be specified via an fd, which will be used as the mountpoint for backing the shmem file associated with a restrictedmem fd.
This will help restrictedmem fds inherit the properties of the provided tmpfs mounts, for example, hugepage allocation hints, NUMA binding hints, etc.
Permissions for the fd passed to
memfd_restricted()is modeled after theopenat()syscall, since both of these allow creation of a file upon a mount/directory.Permission to reference the mount the fd represents is checked upon fd creation by other syscalls (e.g.
fsmount(),open(), oropen_tree(), etc) and any process that can presentmemfd_restricted()with a valid fd is expected to have obtained permission to use the mount represented by the fd. This behavior is intended to parallel that of theopenat()syscall.
memfd_restricted()will check that the tmpfs superblock is writable, and that the mount is also writable, before attempting to create a restrictedmem file on the mount.Signed-off-by Ackerley Tng <[email protected]>--- include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 ++++ mm/restrictedmem.c | 74 +++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/restrictedmem.hinclude/linux/syscalls.h +1
-1@@ -1056,7 +1056,7 @@ asmlinkage long sys_memfd_secret(unsigned int flags);asmlinkage long sys_set_mempolicy_home_node (unsigned long start,unsigned long len,unsigned long home_node,unsigned long flags); -asmlinkage long sys_memfd_restricted (unsigned int flags); +asmlinkage long sys_memfd_restricted (unsigned int flags,int mount_fd);/* * Architecture-specific system calls include/uapi/linux/restrictedmem.h new file +8
-0@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_USERMNT 0x0001U + +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */ mm/restrictedmem.c +27
-6@@ -1,11 +1,12 @@// SPDX-License-Identifier: GPL-2.0 -#include "linux/sbitmap.h" +#include <linux/namei.h> #include <linux/pagemap.h> #include <linux/pseudo_fs.h> #include <linux/shmem_fs.h> #include <linux/syscalls.h> #include <uapi/linux/falloc.h> #include <uapi/linux/magic.h> +#include <uapi/linux/restrictedmem.h> #include <linux/restrictedmem.h> struct restrictedmem {@@ -189,19 +190,20 @@ static struct file *restrictedmem_file_create(struct file *memfd)return file; } -SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err; -if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file =shmem_file_setup_with_mnt (mount,"memfd:restrictedmem" ,0 ,VM_NORESERVE ); +else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) { err =PTR_ERR (file); goto err_fd;@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags)return err; } +static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ; +} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root; +} + +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ;
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 256
Quoted 10 lines
mm/restrictedmem.c +10
-0+ + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt)) + goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC );
Why MAY_EXEC?
Quoted 5 lines
mm/restrictedmem.c +5
-0+if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret))
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 271
Quoted 10 lines
mm/restrictedmem.c +10
-0+ goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f); + +return ret; +}
We need review from fs folks. Look mostly sensible, but I have no experience in fs.
Quoted 4 lines
mm/restrictedmem.c +4
-0+ +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ +if (flags & ~RMFD_USERMNT )
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 285
Quoted 10 lines
mm/restrictedmem.c +10
-0+return -EINVAL ; + +if (flags ==RMFD_USERMNT ) { +if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + }
Maybe restructure with single restrictedmem_create() call?
struct vfsmount *mnt = NULL;
if (flags == RMFD_USERMNT) {
...
mnt = ...();
}
return restrictedmem_create(mnt);Quoted 7 lines
mm/restrictedmem.c +2
-1+} +int restrictedmem_bind (struct file *file,pgoff_t start,pgoff_t end,struct restrictedmem_notifier *notifier,bool exclusive) { --2.40.0.348.gf938b09366-goog
Signature · 1 line
Kiryl Shutsemau / Kirill A. Shutemov
Re: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Thanks for reviewing these patches!
Quoted 17 lines · Kirill A. Shutemov
Quoted 15 lines · Ackerley Tng
...
mm/restrictedmem.c +13
-0+static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ; + + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out;
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 256
Quoted 6 lines
mm/restrictedmem.c +6
-0+ + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt)) + goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC );Why
MAY_EXEC?
Christian pointed out that this check does not make sense, I'll be removing the entire check in the next revision.
Quoted 36 lines
Quoted 5 lines
mm/restrictedmem.c +5
-0+if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret))mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 271
Quoted 10 lines
mm/restrictedmem.c +10
-0+ goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f); + +return ret; +}We need review from fs folks. Look mostly sensible, but I have no experience in fs.
Quoted 4 lines
mm/restrictedmem.c +4
-0+ +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ +if (flags & ~RMFD_USERMNT )mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 285
Quoted 10 lines
mm/restrictedmem.c +10
-0+return -EINVAL ; + +if (flags ==RMFD_USERMNT ) { +if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + }Maybe restructure with single
restrictedmem_create()call?
Quoted 8 lines
struct vfsmount *mnt = NULL;
if (flags == RMFD_USERMNT) {
...
mnt = ...();
}
return restrictedmem_create(mnt);Will do so in the next revision.
Quoted 7 lines
Quoted 7 lines
mm/restrictedmem.c +2
-1+} +int restrictedmem_bind (struct file *file,pgoff_t start,pgoff_t end,struct restrictedmem_notifier *notifier,bool exclusive) { --2.40.0.348.gf938b09366-goog
Christian Brauner
maintainerRe: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
In reply to Ackerley TngQuoted 83 lines · Ackerley Tng
By default, the backing shmem file for a restrictedmem fd is created on shmem's kernel space mount.
With this patch, an optional tmpfs mount can be specified via an fd, which will be used as the mountpoint for backing the shmem file associated with a restrictedmem fd.
This will help restrictedmem fds inherit the properties of the provided tmpfs mounts, for example, hugepage allocation hints, NUMA binding hints, etc.
Permissions for the fd passed to
memfd_restricted()is modeled after theopenat()syscall, since both of these allow creation of a file upon a mount/directory.Permission to reference the mount the fd represents is checked upon fd creation by other syscalls (e.g.
fsmount(),open(), oropen_tree(), etc) and any process that can presentmemfd_restricted()with a valid fd is expected to have obtained permission to use the mount represented by the fd. This behavior is intended to parallel that of theopenat()syscall.
memfd_restricted()will check that the tmpfs superblock is writable, and that the mount is also writable, before attempting to create a restrictedmem file on the mount.Signed-off-by Ackerley Tng <[email protected]>--- include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 ++++ mm/restrictedmem.c | 74 +++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/restrictedmem.hinclude/linux/syscalls.h +1
-1@@ -1056,7 +1056,7 @@ asmlinkage long sys_memfd_secret(unsigned int flags);asmlinkage long sys_set_mempolicy_home_node (unsigned long start,unsigned long len,unsigned long home_node,unsigned long flags); -asmlinkage long sys_memfd_restricted (unsigned int flags); +asmlinkage long sys_memfd_restricted (unsigned int flags,int mount_fd);/* * Architecture-specific system calls include/uapi/linux/restrictedmem.h new file +8
-0@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_USERMNT 0x0001U + +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */ mm/restrictedmem.c +2
-1@@ -1,11 +1,12 @@// SPDX-License-Identifier: GPL-2.0 -#include "linux/sbitmap.h" +#include <linux/namei.h> #include <linux/pagemap.h> #include <linux/pseudo_fs.h> #include <linux/shmem_fs.h> #include <linux/syscalls.h> #include <uapi/linux/falloc.h> #include <uapi/linux/magic.h> +#include <uapi/linux/restrictedmem.h> #include <linux/restrictedmem.h> struct restrictedmem {@@ -189,19 +190,20 @@ static struct file *restrictedmem_file_create(struct file *memfd)return file; }
mm/restrictedmem.c static struct file *restrictedmem_file_create(struct file *memfd) · line 198
Quoted 10 lines
mm/restrictedmem.c +1
-4-SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err; -if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );
Any reasons the file descriptors aren't O_CLOEXEC by default? I don't see any reasons why we should introduce new fdtypes that aren't O_CLOEXEC by default. The "don't mix-and-match" train has already left the station anyway as we do have seccomp noitifer fds and pidfds both of which are O_CLOEXEC by default.
Quoted 13 lines
mm/restrictedmem.c +5
-1if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file =shmem_file_setup_with_mnt (mount,"memfd:restrictedmem" ,0 ,VM_NORESERVE ); +else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) { err =PTR_ERR (file); goto err_fd;
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 230
Quoted 6 lines
mm/restrictedmem.c +3
-0@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags)return err; } +static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ;
This can just be if (mnt->mnt_sb->s_magic == TMPFS_MAGIC).
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 235
Quoted 5 lines
mm/restrictedmem.c +5
-0+} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root;
mount -t tmpfs tmpfs /mnt
touch /mnt/bla
touch /mnt/ble
mount --bind /mnt/bla /mnt/ble
fd = open("/mnt/ble")fd_restricted = memfd_restricted(fd)
IOW, this doesn't restrict it to the tmpfs root. It only restricts it to paths that refer to the root of any tmpfs mount. To exclude bind-mounts that aren't bind-mounts of the whole filesystem you want:
path->dentry == path->mnt->mnt_root &&path->mnt->mnt_root == path->mnt->mnt_sb->s_root
Quoted 11 lines
mm/restrictedmem.c +11
-0+} + +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ;
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 256
Quoted 10 lines
mm/restrictedmem.c +10
-0+ + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt)) + goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC );
With the current semantics you're asking whether you have write permissions on the /mnt/ble file in order to get answer to the question whether you're allowed to create an unlinked restricted memory file. That doesn't make much sense afaict.
Quoted 12 lines
mm/restrictedmem.c +12
-0+if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret)) + goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f);
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 278
Quoted 10 lines
mm/restrictedmem.c +10
-0+ +return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ +if (flags & ~RMFD_USERMNT ) +return -EINVAL ; + +if (flags ==RMFD_USERMNT ) {
Why do you even need this flag? It seems that @mount_fd being < 0 is sufficient to indicate that a new restricted memory fd is supposed to be created in the system instance.
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 286
Quoted 8 lines
mm/restrictedmem.c +8
-0+if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + } +}
I have to say that I'm very confused by all of this the more I look at it.
Effectively memfd restricted functions as a wrapper filesystem around the tmpfs filesystem. This is basically a weird overlay filesystem. You're allocating tmpfs files that you stash in restrictedmem files. I have to say that this seems very hacky. I didn't get this at all at first.
So what does the caller get if they call statx() on a restricted memfd? Do they get the device number of the tmpfs mount and the inode numbers of the tmpfs mount? Because it looks like they would:
static int restrictedmem_getattr(struct user_namespace *mnt_userns,
const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags)
{
struct inode *inode = d_inode(path->dentry);
struct restrictedmem *rm = inode->i_mapping->private_data;
struct file *memfd = rm->memfd;
return memfd->f_inode->i_op->getattr(mnt_userns, path, stat,
request_mask, query_flags);That @memfd would be a struct file allocated in a tmpfs instance, no? So you'd be calling the inode operation of the tmpfs file meaning that struct kstat will be filled up with the info from the tmpfs instance.
But then if I call statfs() and check the fstype I would get RESTRICTEDMEM_MAGIC, no? This is... unorthodox?
I'm honestly puzzled and this sounds really strange. There must be a better way to implement all of this.
Shouldn't you try and make this a part of tmpfs proper? Make a really separate filesystem and add a memfs library that both tmpfs and restrictedmemfs can use? Add a mount option to tmpfs that makes it a restricted tmpfs?
Christian Brauner
maintainerRe: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Quoted 221 lines · Christian Brauner
Quoted 83 lines · Ackerley Tng
By default, the backing shmem file for a restrictedmem fd is created on shmem's kernel space mount.
With this patch, an optional tmpfs mount can be specified via an fd, which will be used as the mountpoint for backing the shmem file associated with a restrictedmem fd.
This will help restrictedmem fds inherit the properties of the provided tmpfs mounts, for example, hugepage allocation hints, NUMA binding hints, etc.
Permissions for the fd passed to
memfd_restricted()is modeled after theopenat()syscall, since both of these allow creation of a file upon a mount/directory.Permission to reference the mount the fd represents is checked upon fd creation by other syscalls (e.g.
fsmount(),open(), oropen_tree(), etc) and any process that can presentmemfd_restricted()with a valid fd is expected to have obtained permission to use the mount represented by the fd. This behavior is intended to parallel that of theopenat()syscall.
memfd_restricted()will check that the tmpfs superblock is writable, and that the mount is also writable, before attempting to create a restrictedmem file on the mount.Signed-off-by Ackerley Tng <[email protected]>--- include/linux/syscalls.h | 2 +- include/uapi/linux/restrictedmem.h | 8 ++++ mm/restrictedmem.c | 74 +++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/restrictedmem.hinclude/linux/syscalls.h +1
-1@@ -1056,7 +1056,7 @@ asmlinkage long sys_memfd_secret(unsigned int flags);asmlinkage long sys_set_mempolicy_home_node (unsigned long start,unsigned long len,unsigned long home_node,unsigned long flags); -asmlinkage long sys_memfd_restricted (unsigned int flags); +asmlinkage long sys_memfd_restricted (unsigned int flags,int mount_fd);/* * Architecture-specific system calls include/uapi/linux/restrictedmem.h new file +8
-0@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _UAPI_LINUX_RESTRICTEDMEM_H +#define _UAPI_LINUX_RESTRICTEDMEM_H + +/* flags for memfd_restricted */ +#define RMFD_USERMNT 0x0001U + +#endif /* _UAPI_LINUX_RESTRICTEDMEM_H */ mm/restrictedmem.c +2
-1@@ -1,11 +1,12 @@// SPDX-License-Identifier: GPL-2.0 -#include "linux/sbitmap.h" +#include <linux/namei.h> #include <linux/pagemap.h> #include <linux/pseudo_fs.h> #include <linux/shmem_fs.h> #include <linux/syscalls.h> #include <uapi/linux/falloc.h> #include <uapi/linux/magic.h> +#include <uapi/linux/restrictedmem.h> #include <linux/restrictedmem.h> struct restrictedmem {@@ -189,19 +190,20 @@ static struct file *restrictedmem_file_create(struct file *memfd)return file; }mm/restrictedmem.c static struct file *restrictedmem_file_create(struct file *memfd) · line 198
Quoted 10 lines
mm/restrictedmem.c +1
-4-SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err; -if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );Any reasons the file descriptors aren't
O_CLOEXECby default? I don't see any reasons why we should introduce new fdtypes that aren'tO_CLOEXECby default. The "don't mix-and-match" train has already left the station anyway as we do have seccomp noitifer fds and pidfds both of which areO_CLOEXECby default.Quoted 13 lines
mm/restrictedmem.c +5
-1if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file =shmem_file_setup_with_mnt (mount,"memfd:restrictedmem" ,0 ,VM_NORESERVE ); +else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) { err =PTR_ERR (file); goto err_fd;mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 230
Quoted 6 lines
mm/restrictedmem.c +3
-0@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags)return err; } +static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ;This can just be if (
mnt->mnt_sb->s_magic==TMPFS_MAGIC).mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 235
Quoted 5 lines
mm/restrictedmem.c +5
-0+} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root;mount -t tmpfs tmpfs /mnt
touch/mnt/bla
touch/mnt/ble
mount --bind/mnt/bla/mnt/ble
fd =open("/mnt/ble")fd_restricted=memfd_restricted(fd)IOW, this doesn't restrict it to the tmpfs root. It only restricts it to paths that refer to the root of any tmpfs mount. To exclude bind-mounts that aren't bind-mounts of the whole filesystem you want:
path->dentry==path->mnt->mnt_root&&path->mnt->mnt_root==path->mnt->mnt_sb->s_rootQuoted 11 lines
mm/restrictedmem.c +11
-0+} + +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ;mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 256
Quoted 10 lines
mm/restrictedmem.c +10
-0+ + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt)) + goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC );With the current semantics you're asking whether you have write permissions on the
/mnt/blefile in order to get answer to the question whether you're allowed to create an unlinked restricted memory file. That doesn't make much sense afaict.Quoted 12 lines
mm/restrictedmem.c +12
-0+if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret)) + goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f);mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 278
Quoted 10 lines
mm/restrictedmem.c +10
-0+ +return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{ +if (flags & ~RMFD_USERMNT ) +return -EINVAL ; + +if (flags ==RMFD_USERMNT ) {Why do you even need this flag? It seems that @mount_fd being < 0 is sufficient to indicate that a new restricted memory fd is supposed to be created in the system instance.
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 286
Quoted 8 lines
mm/restrictedmem.c +8
-0+if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + } +}I have to say that I'm very confused by all of this the more I look at it.
Effectively memfd restricted functions as a wrapper filesystem around the tmpfs filesystem. This is basically a weird overlay filesystem. You're allocating tmpfs files that you stash in restrictedmem files. I have to say that this seems very hacky. I didn't get this at all at first.
So what does the caller get if they call
statx()on a restricted memfd? Do they get the device number of the tmpfs mount and the inode numbers of the tmpfs mount? Because it looks like they would:static int restrictedmem_getattr(struct
user_namespace*mnt_userns,
const struct path *path, struct kstat *stat, u32request_mask, unsigned intquery_flags)
Quoted 6 lines
{
struct inode *inode = d_inode(path->dentry);
struct restrictedmem *rm = inode->i_mapping->private_data;
struct file *memfd = rm->memfd;
return memfd->f_inode->i_op->getattr(mnt_userns, path, stat,This is pretty broken btw, because @path refers to a restrictedmem path which you're passing to a tmpfs iop...
I see that in
return memfd->f_inode->i_op->getattr(mnt_userns, &memfd->f_path, stat, request_mask, query_flags);
this if fixed but still, this is... not great.
Quoted 16 lines
request_mask, query_flags);That @memfd would be a struct file allocated in a tmpfs instance, no? So you'd be calling the inode operation of the tmpfs file meaning that struct kstat will be filled up with the info from the tmpfs instance.
But then if I call
statfs()and check the fstype I would getRESTRICTEDMEM_MAGIC, no? This is... unorthodox?I'm honestly puzzled and this sounds really strange. There must be a better way to implement all of this.
Shouldn't you try and make this a part of tmpfs proper? Make a really separate filesystem and add a memfs library that both tmpfs and restrictedmemfs can use? Add a mount option to tmpfs that makes it a restricted tmpfs?
Re: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Thanks again for your review!
Quoted 11 lines · Christian Brauner
Quoted 10 lines · Christian Brauner
Quoted 9 lines · Ackerley Tng
...
mm/restrictedmem.c +1
-1-SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err;
Quoted 10 lines
Quoted 10 lines
mm/restrictedmem.c static struct file *restrictedmem_file_create(struct file *memfd) · line 198
Quoted 4 lines
mm/restrictedmem.c +0
-3-if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );Any reasons the file descriptors aren't
O_CLOEXECby default? I don't see any reasons why we should introduce new fdtypes that aren'tO_CLOEXECby default. The "don't mix-and-match" train has already left the station anyway as we do have seccomp noitifer fds and pidfds both of which areO_CLOEXECby default.
Thanks for pointing this out. I agree with using O_CLOEXEC, but didn’t notice this before. Let us discuss this under the original series at [1].
Quoted 15 lines
Quoted 15 lines
mm/restrictedmem.c static struct file *restrictedmem_file_create(struct file *memfd)
Quoted 6 lines
mm/restrictedmem.c +2
-1if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file = shmem_file_setup_with_mnt(mount, "memfd:restrictedmem", 0,
VM_NORESERVE);mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int,
Quoted 7 lines
mm/restrictedmem.c +3
-0+else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) { err =PTR_ERR (file); goto err_fd;@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsigned int,flags)
Quoted 8 lines
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 230
Quoted 6 lines
mm/restrictedmem.c +3
-0return err; } +static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ;This can just be if (
mnt->mnt_sb->s_magic==TMPFS_MAGIC).
Will simplify this in the next revision.
Quoted 13 lines
Quoted 12 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 235
Quoted 5 lines
mm/restrictedmem.c +5
-0+} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root;mount -t tmpfs tmpfs /mnt
touch/mnt/bla
touch/mnt/ble
mount --bind/mnt/bla/mnt/ble
fd =open("/mnt/ble")fd_restricted=memfd_restricted(fd)
Quoted 6 lines
Quoted 6 lines
IOW, this doesn't restrict it to the tmpfs root. It only restricts it to paths that refer to the root of any tmpfs mount. To exclude bind-mounts that aren't bind-mounts of the whole filesystem you want:
path->dentry==path->mnt->mnt_root&&path->mnt->mnt_root==path->mnt->mnt_sb->s_root
Will adopt this in the next revision and add a selftest to check this. Thanks for pointing this out!
Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
mm/restrictedmem.c +18
-0+} + +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ; + + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt))
Quoted 8 lines
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 256
Quoted 3 lines
mm/restrictedmem.c +3
-0+ goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC );With the current semantics you're asking whether you have write permissions on the
/mnt/blefile in order to get answer to the question whether you're allowed to create an unlinked restricted memory file. That doesn't make much sense afaict.
That's true. Since mnt_want_write() already checks for write permissions and this syscall creates an unlinked file on the mount, we don't have to check permissions on the file then. Will remove this in the next revision!
Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
mm/restrictedmem.c +18
-0+if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret)) + goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f); + +return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{
Quoted 8 lines
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 278
Quoted 4 lines
mm/restrictedmem.c +4
-0+if (flags & ~RMFD_USERMNT ) +return -EINVAL ; + +if (flags ==RMFD_USERMNT ) {Why do you even need this flag? It seems that @mount_fd being < 0 is sufficient to indicate that a new restricted memory fd is supposed to be created in the system instance.
I'm hoping to have this patch series merged after Chao's patch series introduces the memfd_restricted() syscall [1].
This flag is necessary to indicate the validity of the second argument.
With this flag, we can definitively return an error if the fd is invalid, which I think is a better experience for the userspace programmer than if we just silently default to the kernel mount when the fd provided is invalid.
Quoted 34 lines
Quoted 24 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 286
Quoted 8 lines
mm/restrictedmem.c +8
-0+if (mount_fd <0 ) +return -EINVAL ; + +return restrictedmem_create_on_user_mount (mount_fd); + }else { +return restrictedmem_create (NULL ); + } +}I have to say that I'm very confused by all of this the more I look at it.
Effectively memfd restricted functions as a wrapper filesystem around the tmpfs filesystem. This is basically a weird overlay filesystem. You're allocating tmpfs files that you stash in restrictedmem files. I have to say that this seems very hacky. I didn't get this at all at first.
So what does the caller get if they call
statx()on a restricted memfd? Do they get the device number of the tmpfs mount and the inode numbers of the tmpfs mount? Because it looks like they would:static int restrictedmem_getattr(struct
user_namespace*mnt_userns,
const struct path *path, struct kstat *stat, u32request_mask, unsigned intquery_flags)Quoted 6 lines
{ struct inode *inode = d_inode(path->dentry); struct restrictedmem *rm = inode->i_mapping->private_data; struct file *memfd = rm->memfd; return memfd->f_inode->i_op->getattr(mnt_userns, path, stat,This is pretty broken btw, because @path refers to a restrictedmem path which you're passing to a tmpfs iop...
Quoted 6 lines
I see that in
return memfd->f_inode->i_op->getattr(mnt_userns, &memfd->f_path, stat, request_mask, query_flags);this if fixed but still, this is... not great.
Thanks, this will be fixed in the next revision by rebasing on Chao's latest code.
Quoted 9 lines
Quoted 8 lines
request_mask, query_flags);That @memfd would be a struct file allocated in a tmpfs instance, no? So you'd be calling the inode operation of the tmpfs file meaning that struct kstat will be filled up with the info from the tmpfs instance.
But then if I call
statfs()and check the fstype I would getRESTRICTEDMEM_MAGIC, no? This is... unorthodox?
Quoted 7 lines
Quoted 7 lines
I'm honestly puzzled and this sounds really strange. There must be a better way to implement all of this.
Shouldn't you try and make this a part of tmpfs proper? Make a really separate filesystem and add a memfs library that both tmpfs and restrictedmemfs can use? Add a mount option to tmpfs that makes it a restricted tmpfs?
This was discussed earlier in the patch series introducing memfd_restricted and this approach was taken to better manage ownership of required functionalities between two subsystems. Please see discussion beginning [2]
[1] -> https://lore.kernel.org/lkml/[email protected]/T/.
[2] -> https://lore.kernel.org/lkml/[email protected]/
Christian Brauner
maintainerRe: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Quoted 143 lines · Ackerley Tng
Thanks again for your review!
Quoted 11 lines · Christian Brauner
Quoted 10 lines · Christian Brauner
Quoted 9 lines · Ackerley Tng
...
mm/restrictedmem.c +1
-1-SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) +static int restrictedmem_create (struct vfsmount *mount) {struct file *file, *restricted_file;int fd, err;Quoted 10 lines
Quoted 10 lines
mm/restrictedmem.c static struct file *restrictedmem_file_create(struct file *memfd) · line 198
Quoted 4 lines
mm/restrictedmem.c +0
-3-if (flags) -return -EINVAL ; - fd =get_unused_fd_flags (0 );Any reasons the file descriptors aren't
O_CLOEXECby default? I don't see any reasons why we should introduce new fdtypes that aren'tO_CLOEXECby default. The "don't mix-and-match" train has already left the station anyway as we do have seccomp noitifer fds and pidfds both of which areO_CLOEXECby default.Thanks for pointing this out. I agree with using
O_CLOEXEC, but didn’t notice this before. Let us discuss this under the original series at [1].Quoted 15 lines
Quoted 15 lines
mm/restrictedmem.c static struct file *restrictedmem_file_create(struct file *memfd)
Quoted 6 lines
mm/restrictedmem.c +2
-1if (fd <0 )return fd; - file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (mount) + file = shmem_file_setup_with_mnt(mount, "memfd:restrictedmem",0,
VM_NORESERVE);mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned
Quoted 7 lines
mm/restrictedmem.c +3
-0+else + file =shmem_file_setup ("memfd:restrictedmem" ,0 ,VM_NORESERVE ); +if (IS_ERR (file)) { err =PTR_ERR (file); goto err_fd;@@ -223,6 +225,66 @@ SYSCALL_DEFINE1(memfd_restricted, unsignedint, flags)
Quoted 8 lines
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 230
Quoted 6 lines
mm/restrictedmem.c +3
-0return err; } +static bool is_shmem_mount (struct vfsmount *mnt) +{ +return mnt && mnt->mnt_sb && mnt->mnt_sb->s_magic ==TMPFS_MAGIC ;This can just be if (
mnt->mnt_sb->s_magic==TMPFS_MAGIC).Will simplify this in the next revision.
Quoted 13 lines
Quoted 12 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 235
Quoted 5 lines
mm/restrictedmem.c +5
-0+} + +static bool is_mount_root (struct file *file) +{ +return file->f_path.dentry == file->f_path.mnt->mnt_root;mount -t tmpfs tmpfs /mnt
touch/mnt/bla
touch/mnt/ble
mount --bind/mnt/bla/mnt/ble
fd =open("/mnt/ble")fd_restricted=memfd_restricted(fd)Quoted 6 lines
Quoted 6 lines
IOW, this doesn't restrict it to the tmpfs root. It only restricts it to paths that refer to the root of any tmpfs mount. To exclude bind-mounts that aren't bind-mounts of the whole filesystem you want:
path->dentry==path->mnt->mnt_root&&path->mnt->mnt_root==path->mnt->mnt_sb->s_rootWill adopt this in the next revision and add a selftest to check this. Thanks for pointing this out!
Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
mm/restrictedmem.c +18
-0+} + +static int restrictedmem_create_on_user_mount (int mount_fd) +{ +int ret; +struct fd f; +struct vfsmount *mnt; + + f =fdget_raw (mount_fd); +if (!f.file) +return -EBADF ; + + ret = -EINVAL ; +if (!is_mount_root (f.file)) + goto out; + + mnt = f.file->f_path.mnt; +if (!is_shmem_mount (mnt))Quoted 8 lines
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 256
Quoted 3 lines
mm/restrictedmem.c +3
-0+ goto out; + + ret =file_permission (f.file,MAY_WRITE |MAY_EXEC );With the current semantics you're asking whether you have write permissions on the
/mnt/blefile in order to get answer to the question whether you're allowed to create an unlinked restricted memory file. That doesn't make much sense afaict.That's true. Since
mnt_want_write()already checks for write permissions and this syscall creates an unlinked file on the mount, we don't have to check permissions on the file then. Will remove this in the next revision!Quoted 18 lines
Quoted 18 lines
Quoted 18 lines
mm/restrictedmem.c +18
-0+if (ret) + goto out; + + ret =mnt_want_write (mnt); +if (unlikely (ret)) + goto out; + + ret =restrictedmem_create (mnt); + +mnt_drop_write (mnt); +out: +fdput (f); + +return ret; +} + +SYSCALL_DEFINE2(memfd_restricted, unsigned int, flags, int, mount_fd) +{Quoted 8 lines
Quoted 8 lines
mm/restrictedmem.c SYSCALL_DEFINE1(memfd_restricted, unsigned int, flags) · line 278
Quoted 4 lines
mm/restrictedmem.c +4
-0+if (flags & ~RMFD_USERMNT ) +return -EINVAL ; + +if (flags ==RMFD_USERMNT ) {Why do you even need this flag? It seems that @mount_fd being < 0 is sufficient to indicate that a new restricted memory fd is supposed to be created in the system instance.
Quoted 2 lines
I'm hoping to have this patch series merged after Chao's patch series introduces the
memfd_restricted()syscall [1].
I'm curious, is there an LSFMM session for this?
Re: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Quoted 6 lines · Christian Brauner
Quoted 4 lines · Ackerley Tng
...
Quoted 2 lines
Quoted 2 lines
Why do you even need this flag? It seems that @mount_fd being < 0 is sufficient to indicate that a new restricted memory fd is supposed
Quoted 8 lines
Quoted 6 lines
to be
Quoted 1 line
Quoted 1 line
created in the system instance.
I'm hoping to have this patch series merged after Chao's patch series introduces the
memfd_restricted()syscall [1].I'm curious, is there an LSFMM session for this?
As far as I know, there is no LSFMM session for this.
Sean Christopherson
maintainerRe: [RFC PATCH v3 1/2] mm: restrictedmem: Allow userspace to specify mount for memfd_restricted
Quoted 4 lines · Ackerley Tng
Quoted 1 line · Christian Brauner
I'm curious, is there an LSFMM session for this?
As far as I know, there is no LSFMM session for this.
Correct, no LSFMM session. In hindsight, that's obviously something we should have pursued :-(
RFC PATCH v3 2/2 selftests: restrictedmem: Check hugepage-ness of shmem file backing restrictedmem fd
In reply to Ackerley TngFor memfd_restricted() calls without a userspace mount, the backing file should be the shmem mount in the kernel, and the size of backing pages should be as defined by system-wide shmem configuration.
If a userspace mount is provided, the size of backing pages should be as defined in the mount.
Also includes negative tests for invalid inputs, including fds representing read-only superblocks/mounts.
--- tools/testing/selftests/Makefile | 1 + .../selftests/restrictedmem/.gitignore | 3 + .../testing/selftests/restrictedmem/Makefile | 15 + .../testing/selftests/restrictedmem/common.c | 9 + .../testing/selftests/restrictedmem/common.h | 8 + .../restrictedmem_hugepage_test.c | 486 ++++++++++++++++++ 6 files changed, 522 insertions(+) create mode 100644 tools/testing/selftests/restrictedmem/.gitignore create mode 100644 tools/testing/selftests/restrictedmem/Makefile create mode 100644 tools/testing/selftests/restrictedmem/common.c create mode 100644 tools/testing/selftests/restrictedmem/common.h create mode 100644 tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c
tools/testing/selftests/Makefile +1 -0
@@ -60,6 +60,7 @@ TARGETS += pstore TARGETS += ptrace TARGETS += openat2 TARGETS += resctrl +TARGETS += restrictedmem TARGETS += rlimits TARGETS += rseq TARGETS += rtc
tools/testing/selftests/restrictedmem/.gitignore new file +3 -0
@@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +restrictedmem_hugepage_test
tools/testing/selftests/restrictedmem/Makefile new file +15 -0
@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS = $(KHDR_INCLUDES) +CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -std=gnu99 + +TEST_GEN_PROGS += restrictedmem_hugepage_test + +include ../lib.mk + +EXTRA_CLEAN = $(OUTPUT)/common.o + +$(OUTPUT)/common.o: common.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -ffreestanding $< -o $@ + +$(TEST_GEN_PROGS): $(OUTPUT)/common.o
tools/testing/selftests/restrictedmem/common.c new file +9 -0
@@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <sys/syscall.h> +#include <unistd.h> + +int memfd_restricted (unsigned int flags,int mount_fd) +{ +return syscall (__NR_memfd_restricted, flags, mount_fd); +}
tools/testing/selftests/restrictedmem/common.h new file +8 -0
@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef SELFTESTS_RESTRICTEDMEM_COMMON_H +#define SELFTESTS_RESTRICTEDMEM_COMMON_H + +int memfd_restricted (unsigned int flags,int mount_fd); + +#endif // SELFTESTS_RESTRICTEDMEM_COMMON_H
tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c new file +486 -0
486 lines not on this page · raw message
Signature · 1 line
2.40.0.348.gf938b09366-goog
Re: [RFC PATCH v3 2/2] selftests: restrictedmem: Check hugepage-ness of shmem file backing restrictedmem fd
Quoted 10 lines · Ackerley Tng
For
memfd_restricted()calls without a userspace mount, the backing file should be the shmem mount in the kernel, and the size of backing pages should be as defined by system-wide shmem configuration.If a userspace mount is provided, the size of backing pages should be as defined in the mount.
Also includes negative tests for invalid inputs, including fds representing read-only superblocks/mounts.
When you talk about "hugepage" in this patch, do you mean THP or hugetlb? I suspect thp, so it might be better to spell that out. IIRC, there are plans to support actual huge pages in the future, at which point "hugepage" terminology could be misleading.
Quoted 578 lines
Signed-off-by Ackerley Tng <[email protected]>--- tools/testing/selftests/Makefile | 1 + .../selftests/restrictedmem/.gitignore | 3 + .../testing/selftests/restrictedmem/Makefile | 15 + .../testing/selftests/restrictedmem/common.c | 9 + .../testing/selftests/restrictedmem/common.h | 8 + .../restrictedmem_hugepage_test.c | 486 ++++++++++++++++++ 6 files changed, 522 insertions(+)create mode 100644
tools/testing/selftests/restrictedmem/.gitignorecreate mode 100644tools/testing/selftests/restrictedmem/Makefile
create mode 100644tools/testing/selftests/restrictedmem/common.c
create mode 100644tools/testing/selftests/restrictedmem/common.h
create mode 100644tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.ctools/testing/selftests/Makefile +1
-0@@ -60,6 +60,7 @@ TARGETS += pstore TARGETS += ptrace TARGETS += openat2 TARGETS += resctrl +TARGETS += restrictedmem TARGETS += rlimits TARGETS += rseq TARGETS += rtctools/testing/selftests/restrictedmem/.gitignore new file +3
-0@@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +restrictedmem_hugepage_testtools/testing/selftests/restrictedmem/Makefile new file +15
-0@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 + +CFLAGS = $(KHDR_INCLUDES) +CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -std=gnu99 + +TEST_GEN_PROGS += restrictedmem_hugepage_test + +include ../lib.mk + +EXTRA_CLEAN = $(OUTPUT)/common.o + +$(OUTPUT)/common.o: common.c + $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -ffreestanding $< -o $@ + +$(TEST_GEN_PROGS): $(OUTPUT)/common.otools/testing/selftests/restrictedmem/common.c new file +9
-0@@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <sys/syscall.h> +#include <unistd.h> + +int memfd_restricted (unsigned int flags,int mount_fd) +{ +return syscall (__NR_memfd_restricted, flags, mount_fd); +}tools/testing/selftests/restrictedmem/common.h new file +8
-0@@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef SELFTESTS_RESTRICTEDMEM_COMMON_H +#define SELFTESTS_RESTRICTEDMEM_COMMON_H + +int memfd_restricted (unsigned int flags,int mount_fd); + +#endif // SELFTESTS_RESTRICTEDMEM_COMMON_H tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c new file +486
-0@@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define _GNU_SOURCE/* for O_PATH */ +#define _POSIX_C_SOURCE/* for PATH_MAX */ +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "linux/restrictedmem.h" + +#include "common.h" +#include "../kselftest_harness.h" + +/* +* Expect policy to be one of always, within_size, advise, never, +* deny, force +*/ +#define POLICY_BUF_SIZE 12 + +static int get_hpage_pmd_size (void ) +{ +FILE *fp; +char buf[100 ]; +char *ret; +int size; + + fp =fopen ("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size" ,"r" ); +if (!fp) +return -1 ; + + ret =fgets (buf,100 , fp); +if (ret != buf) { + size =-1 ; + goto out; + } + +if (sscanf (buf,"%d\n" , &size) !=1 ) + size =-1 ; + +out: +fclose (fp); + +return size; +} + +static bool is_valid_shmem_thp_policy (char *policy) +{ +if (strcmp (policy,"always" ) ==0 ) +return true; +if (strcmp (policy,"within_size" ) ==0 ) +return true; +if (strcmp (policy,"advise" ) ==0 ) +return true; +if (strcmp (policy,"never" ) ==0 ) +return true; +if (strcmp (policy,"deny" ) ==0 ) +return true; +if (strcmp (policy,"force" ) ==0 ) +return true; + +return false; +} + +static int get_shmem_thp_policy (char *policy) +{ +FILE *fp; +char buf[100 ]; +char *left =NULL ; +char *right =NULL ; +int ret =-1 ; + + fp =fopen ("/sys/kernel/mm/transparent_hugepage/shmem_enabled" ,"r" ); +if (!fp) +return -1 ; + +if (fgets (buf,100 , fp) != buf) + goto out; + +/* +* Expect shmem_enabled to be of format like "always within_size advise +* [never] deny force" +*/ + left =memchr (buf,'[' ,100 ); +if (!left) + goto out; + + right =memchr (buf,']' ,100 ); +if (!right) + goto out; + +memcpy (policy, left +1 , right - left -1 ); + + ret = !is_valid_shmem_thp_policy (policy); + +out: +fclose (fp); +return ret; +} + +static int write_string_to_file (const char *path,const char *string) +{ +FILE *fp; +size_t len =strlen (string); +int ret =-1 ; + + fp =fopen (path,"w" ); +if (!fp) +return ret; + +if (fwrite (string,1 , len, fp) != len) + goto out; + + ret =0 ; + +out: +fclose (fp); +return ret; +} + +static int set_shmem_thp_policy (char *policy) +{ +int ret =-1 ; +/* +1 for newline */ +char to_write[POLICY_BUF_SIZE +1 ] = {0 }; + +if (!is_valid_shmem_thp_policy (policy)) +return ret; + + ret =snprintf (to_write,POLICY_BUF_SIZE +1 ,"%s\n" , policy); +if (ret !=strlen (policy) +1 ) +return -1 ; + + ret =write_string_to_file ( +"/sys/kernel/mm/transparent_hugepage/shmem_enabled" , to_write); + +return ret; +} + +FIXTURE (reset_shmem_enabled) +{ +char shmem_enabled[POLICY_BUF_SIZE ]; +}; + +FIXTURE_SETUP (reset_shmem_enabled) +{ +memset (self->shmem_enabled,0 ,POLICY_BUF_SIZE ); +ASSERT_EQ (get_shmem_thp_policy (self->shmem_enabled),0 ); +} + +FIXTURE_TEARDOWN (reset_shmem_enabled) +{ +ASSERT_EQ (set_shmem_thp_policy (self->shmem_enabled),0 ); +} + +TEST_F(reset_shmem_enabled, restrictedmem_fstat_shmem_enabled_never) +{ +int fd =-1 ; +struct stat stat; + +ASSERT_EQ (set_shmem_thp_policy ("never" ),0 ); + + fd =memfd_restricted (0 ,-1 ); +ASSERT_GT (fd,0 ); + +ASSERT_EQ (fstat (fd, &stat),0 ); + +/* +* st_blksize is set based on the superblock's s_blocksize_bits. For +* shmem, this is set to PAGE_SHIFT +*/ +ASSERT_EQ (stat.st_blksize,getpagesize ()); + +close (fd); +} + +TEST_F(reset_shmem_enabled, restrictedmem_fstat_shmem_enabled_always) +{ +int fd =-1 ; +struct stat stat; + +ASSERT_EQ (set_shmem_thp_policy ("always" ),0 ); + + fd =memfd_restricted (0 ,-1 ); +ASSERT_GT (fd,0 ); + +ASSERT_EQ (fstat (fd, &stat),0 ); + +ASSERT_EQ (stat.st_blksize,get_hpage_pmd_size ()); + +close (fd); +} + +TEST (restrictedmem_tmpfile_invalid_fd) +{ +int fd =memfd_restricted (RMFD_USERMNT ,-2 ); + +ASSERT_EQ (fd,-1 ); +ASSERT_EQ (errno,EINVAL ); +} + +TEST (restrictedmem_tmpfile_fd_not_a_mount) +{ +int fd =memfd_restricted (RMFD_USERMNT ,STDOUT_FILENO ); + +ASSERT_EQ (fd,-1 ); +ASSERT_EQ (errno,EINVAL ); +} + +TEST (restrictedmem_tmpfile_not_tmpfs_mount) +{ +int fd =-1 ; +int mfd =-1 ; + + mfd =open ("/proc" ,O_PATH ); +ASSERT_NE (mfd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , mfd); + +ASSERT_EQ (fd,-1 ); +ASSERT_EQ (errno,EINVAL ); +} + +FIXTURE (tmpfs_hugepage_sfd) +{ +int sfd; +}; + +FIXTURE_SETUP (tmpfs_hugepage_sfd) +{ + self->sfd =fsopen ("tmpfs" ,0 ); +ASSERT_NE (self->sfd,-1 ); +} + +FIXTURE_TEARDOWN (tmpfs_hugepage_sfd) +{ +EXPECT_EQ (close (self->sfd),0 ); +} + +TEST_F(tmpfs_hugepage_sfd, restrictedmem_fstat_tmpfs_huge_always) +{ +int ret =-1 ; +int fd =-1 ; +int mfd =-1 ; +struct stat stat; + +fsconfig (self->sfd,FSCONFIG_SET_STRING ,"huge" ,"always" ,0 ); +fsconfig (self->sfd,FSCONFIG_CMD_CREATE ,NULL ,NULL ,0 ); + + mfd =fsmount (self->sfd,0 ,0 ); +ASSERT_NE (mfd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , mfd); +ASSERT_GT (fd,0 ); + +/* User can close reference to mount */ + ret =close (mfd); +ASSERT_EQ (ret,0 ); + + ret =fstat (fd, &stat); +ASSERT_EQ (ret,0 ); +ASSERT_EQ (stat.st_blksize,get_hpage_pmd_size ()); + +close (fd); +} + +TEST_F(tmpfs_hugepage_sfd, restrictedmem_fstat_tmpfs_huge_never) +{ +int ret =-1 ; +int fd =-1 ; +int mfd =-1 ; +struct stat stat; + +fsconfig (self->sfd,FSCONFIG_SET_STRING ,"huge" ,"never" ,0 ); +fsconfig (self->sfd,FSCONFIG_CMD_CREATE ,NULL ,NULL ,0 ); + + mfd =fsmount (self->sfd,0 ,0 ); +ASSERT_NE (mfd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , mfd); +ASSERT_GT (fd,0 ); + +/* User can close reference to mount */ + ret =close (mfd); +ASSERT_EQ (ret,0 ); + + ret =fstat (fd, &stat); +ASSERT_EQ (ret,0 ); +ASSERT_EQ (stat.st_blksize,getpagesize ()); + +close (fd); +} + +TEST_F(tmpfs_hugepage_sfd, restrictedmem_check_mount_flags) +{ +int ret =-1 ; +int fd =-1 ; +int mfd =-1 ; + +fsconfig (self->sfd,FSCONFIG_CMD_CREATE ,NULL ,NULL ,0 ); + + mfd =fsmount (self->sfd,0 ,MOUNT_ATTR_RDONLY ); +ASSERT_NE (mfd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , mfd); +ASSERT_EQ (fd,-1 ); +ASSERT_EQ (errno,EROFS ); + + ret =close (mfd); +ASSERT_EQ (ret,0 ); +} + +TEST_F(tmpfs_hugepage_sfd, restrictedmem_check_superblock_flags) +{ +int ret =-1 ; +int fd =-1 ; +int mfd =-1 ; + +fsconfig (self->sfd,FSCONFIG_SET_FLAG ,"ro" ,NULL ,0 ); +fsconfig (self->sfd,FSCONFIG_CMD_CREATE ,NULL ,NULL ,0 ); + + mfd =fsmount (self->sfd,0 ,0 ); +ASSERT_NE (mfd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , mfd); +ASSERT_EQ (fd,-1 ); +ASSERT_EQ (errno,EROFS ); + + ret =close (mfd); +ASSERT_EQ (ret,0 ); +} + +static bool directory_exists (const char *path) +{ +struct stat sb; + +return stat (path, &sb) ==0 &&S_ISDIR (sb.st_mode); +} + +FIXTURE (tmpfs_hugepage_mount_path) +{ +char *mount_path; +}; + +FIXTURE_SETUP (tmpfs_hugepage_mount_path) +{ +int ret =-1 ; + +/* /tmp is an FHS-mandated world-writable directory */ + self->mount_path ="/tmp/restrictedmem-selftest-mnt" ; + +if (!directory_exists (self->mount_path)) { + ret =mkdir (self->mount_path,0777 ); +ASSERT_EQ (ret,0 ); + } +} + +FIXTURE_TEARDOWN (tmpfs_hugepage_mount_path) +{ +int ret =-1 ; + +if (!directory_exists (self->mount_path)) +return ; + + ret =umount2 (self->mount_path,MNT_FORCE ); +EXPECT_EQ (ret,0 ); +if (ret ==-1 && errno ==EINVAL ) +fprintf (stderr," %s was not mounted\n" , self->mount_path); + + ret =rmdir (self->mount_path); +EXPECT_EQ (ret,0 ); +if (ret ==-1 ) +fprintf (stderr," rmdir(%s) failed: %m\n" , self->mount_path); +} + +/* +* memfd_restricted() syscall can only be used with the fd of the root of the +* mount. When the restrictedmem's fd is open, a user should not be able to +* unmount or remove the mounted directory +*/ +TEST_F(tmpfs_hugepage_mount_path, restrictedmem_umount_rmdir_while_file_open) +{ +int ret =-1 ; +int fd =-1 ; +int mfd =-1 ; +struct stat stat; + + ret =mount ("name" , self->mount_path,"tmpfs" ,0 ,"huge=always" ); +ASSERT_EQ (ret,0 ); + + mfd =open (self->mount_path,O_PATH ); +ASSERT_NE (mfd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , mfd); +ASSERT_GT (fd,0 ); + +/* We don't need this reference to the mount anymore */ + ret =close (mfd); +ASSERT_EQ (ret,0 ); + +/* restrictedmem's fd should still be usable */ + ret =fstat (fd, &stat); +ASSERT_EQ (ret,0 ); +ASSERT_EQ (stat.st_blksize,get_hpage_pmd_size ()); + +/* User should not be able to unmount directory */ + ret =umount2 (self->mount_path,MNT_FORCE ); +ASSERT_EQ (ret,-1 ); +ASSERT_EQ (errno,EBUSY ); + + ret =rmdir (self->mount_path); +ASSERT_EQ (ret,-1 ); +ASSERT_EQ (errno,EBUSY ); + +close (fd); +} + +/* The fd of a file on the mount cannot be provided as mount_fd */ +TEST_F(tmpfs_hugepage_mount_path, restrictedmem_provide_fd_of_file) +{ +int ret =-1 ; +int fd =-1 ; +int ffd =-1 ; +char tmp_file_path[PATH_MAX ] = {0 }; + + ret =mount ("name" , self->mount_path,"tmpfs" ,0 ,"huge=always" ); +ASSERT_EQ (ret,0 ); + +snprintf (tmp_file_path,PATH_MAX ,"%s/tmp-file" , self->mount_path); + ret =write_string_to_file (tmp_file_path,"filler\n" ); +ASSERT_EQ (ret,0 ); + + ffd =open (tmp_file_path,O_RDWR ); +ASSERT_GT (ffd,0 ); + + fd =memfd_restricted (RMFD_USERMNT , ffd); +ASSERT_LT (fd,0 ); +ASSERT_EQ (errno,EINVAL ); + + ret =close (ffd); +ASSERT_EQ (ret,0 ); + +close (fd); +remove (tmp_file_path); +} + +/* The fd of files on the mount cannot be provided as mount_fd */ +TEST_F(tmpfs_hugepage_mount_path, restrictedmem_provide_fd_of_file_in_subdir) +{ +int ret =-1 ; +int fd =-1 ; +int ffd =-1 ; +char tmp_dir_path[PATH_MAX ] = {0 }; +char tmp_file_path[PATH_MAX ] = {0 }; + + ret =mount ("name" , self->mount_path,"tmpfs" ,0 ,"huge=always" ); +ASSERT_EQ (ret,0 ); + +snprintf (tmp_dir_path,PATH_MAX ,"%s/tmp-subdir" , self->mount_path); + ret =mkdir (tmp_dir_path,0777 ); +ASSERT_EQ (ret,0 ); + +snprintf (tmp_file_path,PATH_MAX ,"%s/tmp-subdir/tmp-file" , + self->mount_path); + ret =write_string_to_file (tmp_file_path,"filler\n" ); +ASSERT_EQ (ret,0 ); + + ffd =open (tmp_file_path,O_RDWR ); +ASSERT_NE (ffd,-1 ); + + fd =memfd_restricted (RMFD_USERMNT , ffd); +ASSERT_LT (fd,0 ); +ASSERT_EQ (errno,EINVAL ); + + ret =close (ffd); +ASSERT_EQ (ret,0 ); + +close (fd); +remove (tmp_file_path); +rmdir (tmp_dir_path); +} + +TEST_HARNESS_MAIN
Signature · 3 lines
Thanks, David / dhildenb
Re: [RFC PATCH v3 2/2] selftests: restrictedmem: Check hugepage-ness of shmem file backing restrictedmem fd
Quoted 8 lines · David Hildenbrand
Quoted 6 lines · Ackerley Tng
For
memfd_restricted()calls without a userspace mount, the backing file should be the shmem mount in the kernel, and the size of backing pages should be as defined by system-wide shmem configuration.If a userspace mount is provided, the size of backing pages should be as defined in the mount.
Quoted 8 lines
Quoted 2 lines
Also includes negative tests for invalid inputs, including fds representing read-only superblocks/mounts.
When you talk about "hugepage" in this patch, do you mean THP or hugetlb? I suspect thp, so it might be better to spell that out. IIRC, there are plans to support actual huge pages in the future, at which point "hugepage" terminology could be misleading.
Thanks for pointing this out! I've replaced references to hugepage with thp, please see RFC v4 at
https://lore.kernel.org/lkml/[email protected]/T/
Quoted 17 lines
Quoted 17 lines
Signed-off-by Ackerley Tng <[email protected]>--- tools/testing/selftests/Makefile | 1 + .../selftests/restrictedmem/.gitignore | 3 + .../testing/selftests/restrictedmem/Makefile | 15 + .../testing/selftests/restrictedmem/common.c | 9 + .../testing/selftests/restrictedmem/common.h | 8 + .../restrictedmem_hugepage_test.c | 486 ++++++++++++++++++ 6 files changed, 522 insertions(+)create mode 100644
tools/testing/selftests/restrictedmem/.gitignorecreate mode 100644tools/testing/selftests/restrictedmem/Makefilecreate mode 100644tools/testing/selftests/restrictedmem/common.ccreate mode 100644tools/testing/selftests/restrictedmem/common.hcreate mode 100644tools/testing/selftests/restrictedmem/restrictedmem_hugepage_test.c...