iommufd: vIOMMUs and TSM guest requests for confidential guests
Subsystems (18)
Aneesh Kumar K.V (Arm)
maintainerv7 cover iommufd: vIOMMUs and TSM guest requests for confidential guests
This series adds IOMMUFD and PCI/TSM infrastructure for assigning PCI devices to confidential guests. It includes the IOMMU_VDEVICE_TSM_REQ ioctl. The Arm CCA host backend is supplied in a separate series; this series provides the interfaces it uses.
Physical IOMMU drivers and PCI TSM backends now provide vIOMMU ops before IOMMUFD validates the parent HWPT. These ops provide callbacks for determining the allocation size and initializing the vIOMMU, along with flags specifying whether a parent HWPT is required. A nesting parent is required by default. When the selected ops set IOMMUFD_VIOMMU_NO_HWPT, userspace must supply a zero hwpt_id, and IOMMUFD passes NULL as the parent domain to the initialization callback. Nested HWPT and hardware queue allocation are rejected for a vIOMMU without a parent.
IOMMUFD first queries the TSM attached to the selected PCI device. Lookup pins the backend module before using its vIOMMU ops, and that reference is released after the backend's vIOMMU destruction callback. The backend must keep its TSM registered while the module is pinned. When no TSM provides ops for the requested type, IOMMUFD falls back to the physical IOMMU driver. An error from TSM lookup or querying its ops is returned without falling back.
Vdevice contexts replace the legacy PCI/TSM bind and unbind interface. Context acquisition verifies that the device's current TSM matches the vIOMMU's TSM. An explicit sysfs disconnect returns EBUSY while contexts remain active. Guest requests are dispatched through the vIOMMU ops.
IOMMU_VDEVICE_TSM_REQ checks the guest architecture and the vdevice's supported-operation mask before forwarding userspace buffers to the backend. Request and response lengths are limited to INT_MAX so a successful residue fits in the ioctl return value. The backend supplies the architecture-specific request handling and TSM result code.
Notes:
* Codex was used to assist with commit message formatting and code rearrangement.
* This series also includes patches from the following series to provide the dependencies needed for Sashiko to review the combined changes:
https://lore.kernel.org/all/[email protected]
Changes from v6:
https://lore.kernel.org/all/[email protected]
* Replace the IOMMUFD provider registry with per-device TSM operation lookup.
* Move vIOMMU allocation size and initialization into the selected vIOMMU ops.
* Select parent HWPT policy from the vIOMMU ops flags.
* Pin a vIOMMU's TSM backend module through vIOMMU destruction.
Changes from v5:
https://lore.kernel.org/all/[email protected]
* Replace the TSM bind/unbind interface with reference-counted contexts.
* Add the IOMMUFD vIOMMU provider abstraction.
* Route TSM guest requests through vIOMMU ops.
Changes from v4:
https://lore.kernel.org/all/[email protected]
* Switch VFIO/iommufd to use struct file *kvm_file instead of relying on kvm->users_count references.
* Define TSM request scope values globally in iommufd.
* Rename the ioctl to IOMMU_VDEVICE_TSM_REQ.
* Address other review feedback.
Changes from v2:
https://lore.kernel.org/all/[email protected]
* Bump the series revision to v4 to keep it in sync with the dependent CCA DA patchsets. There was no v3 posting.
* Drop [PATCH v2 1/3] iommufd/viommu: Allow associating a KVM VM fd with a vIOMMU
* Add two new patches to associate a struct kvm * with iommufd objects:
iommufd/device: Associate a kvm pointer to iommufd_device iommufd/viommu: Associate a kvm pointer to iommufd_viommu
* Address review feedback
Changes from v1:
https://lore.kernel.org/all/[email protected]
* Rebase onto the latest kernel
* Address review feedback
* Drop the TSM map ioctl; the KVM prefault patch will be used instead to ensure that private memory is preallocated
Aneesh Kumar K.V (Arm) (8):
tsm: Remove the device from lookup before PCI teardown
iommufd: Add the vdevice TSM request ioctl
PCI/TSM: Remove the legacy guest request interface PCI/TSM: Add vIOMMU-bound contexts for vdevices iommufd/viommu: Select vIOMMU operations before allocation iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
iommufd: Allow vIOMMUs without a parent HWPT
PCI/TSM: wait for vdevice contexts before removing a DSM
Nicolin Chen (1):
iommufd/viommu: Keep a reference to the KVM file
Shameer Kolothum (1):
iommufd/device: Associate KVM file pointer with iommufd_device
Steffen Eiden (6):
KVM: Introduce file_to_kvm_<arch>() infrastructure
KVM: Add file back-pointer to struct kvm
KVM: x86: Use file_to_kvm_x86() in SEV
KVM/vfio: Use file-based reference counting for KVM
KVM: Restrict kvm_get_kvm/kvm_put_kvm export to internal KVM modules
KVM: Remove unused file_is_kvm
Documentation/ABI/testing/sysfs-bus-pci | 17 +- Documentation/userspace-api/iommufd.rst | 44 ++- arch/s390/include/asm/kvm_host_s390.h | 4 +- arch/s390/kvm/s390/pci.c | 9 +- arch/x86/include/asm/kvm_host.h | 2 + arch/x86/include/asm/kvm_page_track.h | 10 +- arch/x86/kvm/Makefile | 4 +- arch/x86/kvm/mmu/page_track.c | 22 +- arch/x86/kvm/svm/sev.c | 8 +- drivers/iommu/amd/iommu.c | 3 +- drivers/iommu/amd/iommufd.c | 18 +- drivers/iommu/amd/iommufd.h | 11 +- drivers/iommu/amd/nested.c | 4 +- .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 37 +- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 7 +- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 16 +- .../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 24 +- drivers/iommu/iommufd/Makefile | 2 + drivers/iommu/iommufd/device.c | 7 +- drivers/iommu/iommufd/hw_pagetable.c | 14 +- drivers/iommu/iommufd/iommufd_private.h | 10 + drivers/iommu/iommufd/main.c | 3 + drivers/iommu/iommufd/selftest.c | 33 +- drivers/iommu/iommufd/tsm.c | 80 ++++ drivers/iommu/iommufd/viommu.c | 117 ++++-- drivers/pci/tsm.c | 362 ++++++++---------- drivers/s390/crypto/vfio_ap_ops.c | 20 +- drivers/vfio/group.c | 11 +- drivers/vfio/iommufd.c | 3 +- drivers/vfio/vfio.h | 12 +- drivers/vfio/vfio_main.c | 57 +-- drivers/virt/coco/tsm-core.c | 55 ++- include/linux/iommu.h | 20 +- include/linux/iommufd.h | 35 +- include/linux/kvm_host.h | 19 +- include/linux/pci-tsm.h | 153 ++++---- include/linux/tsm.h | 46 +++ include/linux/vfio.h | 5 +- include/uapi/linux/iommufd.h | 85 +++- virt/kvm/kvm_main.c | 21 +- virt/kvm/vfio.c | 13 +- 41 files changed, 893 insertions(+), 530 deletions(-) create mode 100644 drivers/iommu/iommufd/tsm.c
base-commit: 551c722f40809618230001baccf219193e22fc5a
Signature · 1 line
2.43.0
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 1/16 KVM: Introduce file_to_kvm_<arch>() infrastructure
From: Steffen Eiden <[email protected]>
Add a macro mechanism that generates an opt-in and arch-namespaced file_to_kvm_<arch>() to convert a file reference to a kvm object if the file handle represents a kvm handle.
Activate for x86 and for s390 (native KVM only).
--- arch/s390/include/asm/kvm_host_s390.h | 2 ++ arch/x86/include/asm/kvm_host.h | 2 ++ arch/x86/kvm/Makefile | 1 + include/linux/kvm_host.h | 12 ++++++++++++ virt/kvm/kvm_main.c | 11 +++++++++++ 5 files changed, 28 insertions(+)
arch/s390/include/asm/kvm_host_s390.h +2 -0
@@ -27,6 +27,8 @@#include <asm/isc.h> #include <asm/guarded_storage.h> +#define kvm_file_to_kvm_arch s390 +#define KVM_HAVE_MMU_RWLOCK #define KVM_MAX_VCPUS 255
arch/x86/include/asm/kvm_host.h +2 -0
@@ -44,6 +44,8 @@#include <hyperv/hvhdk.h> +#define kvm_file_to_kvm_arch x86 +#define __KVM_HAVE_ARCH_VCPU_DEBUGFS/*
arch/x86/kvm/Makefile +1 -0
@@ -61,6 +61,7 @@ exports_grep_trailer := --include='*.[ch]' -nrw $(srctree)/virt/kvm $(srctree)/a -e kvm_page_track_unregister_notifier \ -e kvm_write_track_add_gfn \ -e kvm_write_track_remove_gfn \ + -e kvm_file_to_kvm_fn \ -e kvm_get_kvm \ -e kvm_get_kvm_safe \ -e kvm_put_kvm
include/linux/kvm_host.h +12 -0
@@ -1082,6 +1082,18 @@ void kvm_get_kvm(struct kvm *kvm);bool kvm_get_kvm_safe (struct kvm *kvm);void kvm_put_kvm (struct kvm *kvm);bool file_is_kvm (struct file *file); + +/* +* Architectures define kvm_file_to_kvm_arch to <arch> +* to get a typed, arch-namespaced helper: +* +* struct kvm *file_to_kvm_<arch>(struct file *file) +*/ +#ifdef kvm_file_to_kvm_arch +#define kvm_file_to_kvm_fn CONCATENATE(file_to_kvm_, kvm_file_to_kvm_arch) +struct kvm *kvm_file_to_kvm_fn (struct file *file); +#endif /* kvm_file_to_kvm_arch */ +void kvm_put_kvm_no_destroy (struct kvm *kvm);static inline struct kvm_memslots *__kvm_memslots (struct kvm *kvm,int as_id)
virt/kvm/kvm_main.c +11 -0
@@ -5519,6 +5519,17 @@ bool file_is_kvm(struct file *file) }EXPORT_SYMBOL_FOR_KVM_INTERNAL (file_is_kvm); +#ifdef kvm_file_to_kvm_arch +struct kvm *kvm_file_to_kvm_fn (struct file *file) +{ +if (!file || file->f_op != &kvm_vm_fops) +return NULL ; + +return file->private_data; +} +EXPORT_SYMBOL_GPL (kvm_file_to_kvm_fn); +#endif +static int kvm_dev_ioctl_create_vm (unsigned long type) {char fdname[ITOA_MAX_LEN +1 ];
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 01/16] KVM: Introduce file_to_kvm_<arch>() infrastructure
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
From: Steffen Eiden <[email protected]>
Add a macro mechanism that generates an opt-in and arch-namespaced file_to_kvm_<arch>() to convert a file reference to a kvm object if the file handle represents a kvm handle.
Activate for x86 and for s390 (native KVM only).
Suggested-by Sean Christopherson <[email protected]>Signed-off-by Steffen Eiden <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 2/16 KVM: Add file back-pointer to struct kvm
In reply to Aneesh Kumar K.V (Arm)From: Steffen Eiden <[email protected]>
Add kvm->file as a no-reference back-pointer to the VM file in struct kvm. The pointer is set at VM creation time and cleared under WRITE_ONCE() in kvm_vm_release() before the file is freed. Callers storing the file must take their own reference.
--- include/linux/kvm_host.h | 6 ++++++ virt/kvm/kvm_main.c | 4 ++++ 2 files changed, 10 insertions(+)
include/linux/kvm_host.h +6 -0
@@ -784,6 +784,12 @@ struct kvm {* kvm_swap_active_memslots(). */ struct mutex slots_arch_lock; +/* +* Back-reference to the VM file for subsystems (e.g., VFIO). Holds no +* reference to avoid pinning the VM. Callers storing the file must +* take their own reference. +*/ +struct file *file;struct mm_struct *mm;/* userspace tied to this vm */ unsigned long nr_memslot_pages;/* The two memslot sets - active and inactive (per address space) */
virt/kvm/kvm_main.c +4 -0
@@ -1352,6 +1352,8 @@ static int kvm_vm_release(struct inode *inode, struct file *filp)kvm_irqfd_release (kvm); +WRITE_ONCE (kvm->file,NULL ); +kvm_put_kvm (kvm);return 0 ; }
@@ -5555,6 +5557,8 @@ static int kvm_dev_ioctl_create_vm(unsigned long type) goto put_kvm; } + kvm->file = file; +/* * Don't call kvm_put_kvm anymore at this point; file->f_op is * already set, with ->release() being kvm_vm_release(). In error
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 02/16] KVM: Add file back-pointer to struct kvm
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
From: Steffen Eiden <[email protected]>
Add
kvm->fileas a no-reference back-pointer to the VM file in struct kvm. The pointer is set at VM creation time and cleared underWRITE_ONCE()inkvm_vm_release()before the file is freed. Callers storing the file must take their own reference.Co-developed-by Sean Christopherson <[email protected]>Signed-off-by Sean Christopherson <[email protected]>Signed-off-by Steffen Eiden <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 3/16 KVM: x86: Use file_to_kvm_x86() in SEV
In reply to Aneesh Kumar K.V (Arm)From: Steffen Eiden <[email protected]>
Use the new arch-namespaced helper instead of open-coding the file check and private_data cast separately.
--- arch/x86/kvm/svm/sev.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
arch/x86/kvm/svm/sev.c +4 -4
@@ -2151,10 +2151,10 @@ int sev_vm_move_enc_context_from(struct kvm *kvm, unsigned int source_fd)if (fd_empty (f))return -EBADF ; -if (!file_is_kvm (fd_file (f))) + source_kvm =file_to_kvm_x86 (fd_file (f)); +if (!source_kvm)return -EBADF ; - source_kvm =fd_file (f)->private_data; ret =sev_lock_two_vms (kvm, source_kvm);if (ret)return ret;
@@ -2876,10 +2876,10 @@ int sev_vm_copy_enc_context_from(struct kvm *kvm, unsigned int source_fd)if (fd_empty (f))return -EBADF ; -if (!file_is_kvm (fd_file (f))) + source_kvm =file_to_kvm_x86 (fd_file (f)); +if (!source_kvm)return -EBADF ; - source_kvm =fd_file (f)->private_data; ret =sev_lock_two_vms (kvm, source_kvm);if (ret)return ret;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 03/16] KVM: x86: Use file_to_kvm_x86() in SEV
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
From: Steffen Eiden <[email protected]>
Use the new arch-namespaced helper instead of open-coding the file check and
private_datacast separately.Suggested-by Sean Christopherson <[email protected]>Signed-off-by Steffen Eiden <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 4/16 KVM/vfio: Use file-based reference counting for KVM
In reply to Aneesh Kumar K.V (Arm)From: Steffen Eiden <[email protected]>
Replace manual module reference counting with file-based reference counting for KVM integration. Previously, VFIO used symbol_get() to obtain function pointers for kvm_get_kvm_safe() and kvm_put_kvm(), then manually tracked module references through these symbols. This approach required storing the put_kvm function pointer in each device and carefully managing symbol references.
Pass struct file pointers instead of struct kvm pointers throughout the VFIO-KVM interface, leveraging the kernel's existing file reference counting via get_file()/get_file_active() and fput(). Convert the x86 page-track API and update s390 vfio to use file_to_kvm_<arch>(). This simplifies the code.
--- arch/s390/include/asm/kvm_host_s390.h | 2 +- arch/s390/kvm/s390/pci.c | 9 ++++- arch/x86/include/asm/kvm_page_track.h | 10 +++-- arch/x86/kvm/mmu/page_track.c | 22 +++++++---- drivers/s390/crypto/vfio_ap_ops.c | 20 +++++++--- drivers/vfio/group.c | 11 +++++- drivers/vfio/vfio.h | 12 +++--- drivers/vfio/vfio_main.c | 57 +++++++++------------------ include/linux/vfio.h | 5 +-- virt/kvm/vfio.c | 13 ++++-- 10 files changed, 87 insertions(+), 74 deletions(-)
arch/s390/include/asm/kvm_host_s390.h +1 -1
@@ -722,7 +722,7 @@ static inline void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu) {}void kvm_arch_free_vm (struct kvm *kvm);struct zpci_kvm_hook { -int (*kvm_register)(void *opaque,struct kvm *kvm); +int (*kvm_register)(void *opaque,struct file *kvm_file);void (*kvm_unregister)(void *opaque); };
arch/s390/kvm/s390/pci.c +7 -2
@@ -498,17 +498,22 @@ static void kvm_s390_pci_dev_release(struct zpci_dev *zdev)* available, enable them and let userspace indicate whether or not they will * be used (specify SHM bit to disable). */ -static int kvm_s390_pci_register_kvm (void *opaque,struct kvm *kvm) +static int kvm_s390_pci_register_kvm (void *opaque,struct file *kvm_file) {struct zpci_dev *zdev = opaque; +struct kvm *kvm;int rc;if (!zdev)return -EINVAL ; + kvm =file_to_kvm_s390 (kvm_file); +if (!kvm) +return -ENOENT ; +mutex_lock (&zdev->kzdev_lock); -if (zdev->kzdev || zdev->gisa !=0 || !kvm) { +if (zdev->kzdev || zdev->gisa !=0 ) {mutex_unlock (&zdev->kzdev_lock);return -EINVAL ; }
arch/x86/include/asm/kvm_page_track.h +6 -4
@@ -44,13 +44,15 @@ struct kvm_page_track_notifier_node { struct kvm_page_track_notifier_node *node); }; -int kvm_page_track_register_notifier (struct kvm *kvm, +struct file ; + +int kvm_page_track_register_notifier (struct file *file,struct kvm_page_track_notifier_node *n); -void kvm_page_track_unregister_notifier (struct kvm *kvm, +void kvm_page_track_unregister_notifier (struct file *file,struct kvm_page_track_notifier_node *n); -int kvm_write_track_add_gfn (struct kvm *kvm,gfn_t gfn); -int kvm_write_track_remove_gfn (struct kvm *kvm,gfn_t gfn); +int kvm_write_track_add_gfn (struct file *file,gfn_t gfn); +int kvm_write_track_remove_gfn (struct file *file,gfn_t gfn);#else /* * Allow defining a node in a structure even if page tracking is disabled, e.g.
arch/x86/kvm/mmu/page_track.c +14 -8
@@ -16,6 +16,8 @@#include <linux/kvm_host.h> #include <linux/rculist.h> +#include <asm/kvm_page_track.h> +#include "mmu.h" #include "mmu_internal.h" #include "page_track.h"
@@ -237,10 +239,11 @@ static int kvm_enable_external_write_tracking(struct kvm *kvm)* register the notifier so that event interception for the tracked guest * pages can be received. */ -int kvm_page_track_register_notifier (struct kvm *kvm, +int kvm_page_track_register_notifier (struct file *file,struct kvm_page_track_notifier_node *n) {struct kvm_page_track_notifier_head *head; +struct kvm *kvm =file_to_kvm_x86 (file);int r;if (!kvm || kvm->mm != current->mm)
@@ -252,7 +255,7 @@ int kvm_page_track_register_notifier(struct kvm *kvm,return r; } -kvm_get_kvm (kvm); +get_file (file); head = &kvm->arch.track_notifier_head;
@@ -267,10 +270,11 @@ EXPORT_SYMBOL_GPL(kvm_page_track_register_notifier);* stop receiving the event interception. It is the opposed operation of * kvm_page_track_register_notifier(). */ -void kvm_page_track_unregister_notifier (struct kvm *kvm, +void kvm_page_track_unregister_notifier (struct file *file,struct kvm_page_track_notifier_node *n) {struct kvm_page_track_notifier_head *head; +struct kvm *kvm =file_to_kvm_x86 (file); head = &kvm->arch.track_notifier_head;
@@ -279,7 +283,7 @@ void kvm_page_track_unregister_notifier(struct kvm *kvm,write_unlock (&kvm->mmu_lock);synchronize_srcu (&head->track_srcu); -kvm_put_kvm (kvm); +fput (file); }EXPORT_SYMBOL_GPL (kvm_page_track_unregister_notifier);
@@ -336,11 +340,12 @@ void kvm_page_track_delete_slot(struct kvm *kvm, struct kvm_memory_slot *slot)* add guest page to the tracking pool so that corresponding access on that * page will be intercepted. * -* @kvm: the guest instance we are interested in. +* @file: the VM file of the guest instance we are interested in. * @gfn: the guest page. */ -int kvm_write_track_add_gfn (struct kvm *kvm,gfn_t gfn) +int kvm_write_track_add_gfn (struct file *file,gfn_t gfn) { +struct kvm *kvm =file_to_kvm_x86 (file);struct kvm_memory_slot *slot;int idx;
@@ -366,11 +371,12 @@ EXPORT_SYMBOL_GPL(kvm_write_track_add_gfn);* remove the guest page from the tracking pool which stops the interception * of corresponding access on that page. * -* @kvm: the guest instance we are interested in. +* @file: the VM file of the guest instance we are interested in. * @gfn: the guest page. */ -int kvm_write_track_remove_gfn (struct kvm *kvm,gfn_t gfn) +int kvm_write_track_remove_gfn (struct file *file,gfn_t gfn) { +struct kvm *kvm =file_to_kvm_x86 (file);struct kvm_memory_slot *slot;int idx;
drivers/s390/crypto/vfio_ap_ops.c +14 -6
@@ -1822,17 +1822,27 @@ static const struct attribute_group *vfio_ap_mdev_attr_groups[] = {/** * vfio_ap_mdev_set_kvm - sets all data for @matrix_mdev that are needed -* to manage AP resources for the guest whose state is represented by @kvm +* to manage AP resources for the guest whose state is represented by +* @kvm_file * * @matrix_mdev: a mediated matrix device -* @kvm: reference to KVM instance +* @kvm_file: the KVM VM file this vfio device is associated with * -* Return: 0 if no other mediated matrix device has a reference to @kvm; +* Return: 0 if no other mediated matrix device has a reference to the VM; * otherwise, returns an -EPERM. */ static int vfio_ap_mdev_set_kvm (struct ap_matrix_mdev *matrix_mdev, -struct kvm *kvm) +struct file *kvm_file) { +struct kvm *kvm; + +if (!kvm_file) +return -ENOENT ; + + kvm =file_to_kvm_s390 (kvm_file); +if (!kvm) +return -ENOENT ; +if (kvm->arch.crypto.crycbd) {get_update_locks_for_kvm (kvm);if (kvm->arch.crypto.pqap_hook) {
@@ -1841,7 +1851,6 @@ static int vfio_ap_mdev_set_kvm(struct ap_matrix_mdev *matrix_mdev, } kvm->arch.crypto.pqap_hook = &matrix_mdev->pqap_hook; -kvm_get_kvm (kvm); matrix_mdev->kvm = kvm;vfio_ap_mdev_update_guest_apcb (matrix_mdev);release_update_locks_for_kvm (kvm);
@@ -1894,7 +1903,6 @@ static void vfio_ap_mdev_unset_kvm(struct ap_matrix_mdev *matrix_mdev) matrix_mdev->kvm =NULL ;release_update_locks_for_kvm (kvm); -kvm_put_kvm (kvm); } }
drivers/vfio/group.c +10 -1
@@ -860,11 +860,20 @@ bool vfio_group_enforced_coherent(struct vfio_group *group)return ret; } -void vfio_group_set_kvm (struct vfio_group *group,struct kvm *kvm) +void vfio_group_set_kvm (struct vfio_group *group,struct file *kvm) { +struct file *old; + +if (kvm) +get_file (kvm); +spin_lock (&group->kvm_ref_lock); + old = group->kvm; group->kvm = kvm;spin_unlock (&group->kvm_ref_lock); + +if (old) +fput (old); }/**
drivers/vfio/vfio.h +6 -6
@@ -23,7 +23,7 @@ struct vfio_device_file {u8 access_granted;u32 devid;/* only valid when iommufd is valid */ spinlock_t kvm_ref_lock;/* protect kvm field */ -struct kvm *kvm; +struct file *kvm;struct iommufd_ctx *iommufd;/* protected by struct vfio_device_set::lock */ };
@@ -88,7 +88,7 @@ struct vfio_group {#endif enum vfio_group_type type;struct mutex group_lock; -struct kvm *kvm; +struct file *kvm;struct file *opened_file;struct iommufd_ctx *iommufd;spinlock_t kvm_ref_lock;
@@ -107,7 +107,7 @@ void vfio_device_group_unuse_iommu(struct vfio_device *device);void vfio_df_group_close (struct vfio_device_file *df);struct vfio_group *vfio_group_from_file (struct file *file);bool vfio_group_enforced_coherent (struct vfio_group *group); -void vfio_group_set_kvm (struct vfio_group *group,struct kvm *kvm); +void vfio_group_set_kvm (struct vfio_group *group,struct file *kvm);bool vfio_device_has_container (struct vfio_device *device);int __init vfio_group_init (void );void vfio_group_cleanup (void );
@@ -165,7 +165,7 @@ static inline bool vfio_group_enforced_coherent(struct vfio_group *group)return true; } -static inline void vfio_group_set_kvm (struct vfio_group *group,struct kvm *kvm) +static inline void vfio_group_set_kvm (struct vfio_group *group,struct file *kvm) { }
@@ -429,11 +429,11 @@ static inline void vfio_virqfd_exit(void)#endif #if IS_ENABLED (CONFIG_KVM ) -void vfio_device_get_kvm_safe (struct vfio_device *device,struct kvm *kvm); +void vfio_device_get_kvm_safe (struct vfio_device *device,struct file *kvm);void vfio_device_put_kvm (struct vfio_device *device);#else static inline void vfio_device_get_kvm_safe (struct vfio_device *device, -struct kvm *kvm) +struct file *kvm) { }
drivers/vfio/vfio_main.c +18 -39
105 lines not on this page · raw message
include/linux/vfio.h +2 -3
@@ -54,7 +54,7 @@ struct vfio_device {struct list_head dev_set_list;unsigned int migration_flags;u8 precopy_info_v2; -struct kvm *kvm; +struct file *kvm;/* Members below here are private, not for driver use */ unsigned int index;
@@ -66,7 +66,6 @@ struct vfio_device {unsigned int open_count;struct completion comp;struct iommufd_access *iommufd_access; -void (*put_kvm)(struct kvm *kvm);struct inode *inode;#if IS_ENABLED (CONFIG_IOMMUFD )struct iommufd_device *iommufd_device;
@@ -378,7 +377,7 @@ static inline bool vfio_file_has_dev(struct file *file, struct vfio_device *devi#endif bool vfio_file_is_valid (struct file *file);bool vfio_file_enforced_coherent (struct file *file); -void vfio_file_set_kvm (struct file *file,struct kvm *kvm); +void vfio_file_set_kvm (struct file *vfio_file,struct file *kvm);#define VFIO_PIN_PAGES_MAX_ENTRIES (PAGE_SIZE/sizeof(unsigned long))
virt/kvm/vfio.c +9 -4
@@ -35,15 +35,15 @@ struct kvm_vfio {bool noncoherent; }; -static void kvm_vfio_file_set_kvm (struct file *file,struct kvm *kvm) +static void kvm_vfio_file_set_kvm (struct file *vfio_file,struct file *kvm) { -void (*fn)(struct file *file,struct kvm *kvm); +void (*fn)(struct file *vfio_file,struct file *kvm); fn =symbol_get (vfio_file_set_kvm);if (!fn)return ; -fn (file, kvm); +fn (vfio_file, kvm);symbol_put (vfio_file_set_kvm); }
@@ -144,6 +144,7 @@ static int kvm_vfio_file_add(struct kvm_device *dev, unsigned int fd) {struct kvm_vfio *kv = dev->private;struct kvm_vfio_file *kvf; + struct file *kvm_file __free(fput) = NULL; struct file *filp __free(fput) = NULL; filp =fget (fd);
@@ -154,6 +155,10 @@ static int kvm_vfio_file_add(struct kvm_device *dev, unsigned int fd)if (!kvm_vfio_file_is_valid (filp))return -EINVAL ; + kvm_file =get_file_active (&dev->kvm->file); +if (!kvm_file) +return -ENOENT ; +guard (mutex)(&kv->lock);list_for_each_entry (kvf, &kv->file_list, node) {
@@ -168,7 +173,7 @@ static int kvm_vfio_file_add(struct kvm_device *dev, unsigned int fd) kvf->file =get_file (filp);list_add_tail (&kvf->node, &kv->file_list); -kvm_vfio_file_set_kvm (kvf->file, dev->kvm); +kvm_vfio_file_set_kvm (kvf->file, kvm_file);kvm_vfio_update_coherency (dev);return 0 ;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 04/16] KVM/vfio: Use file-based reference counting for KVM
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] vfio: premature KVM reference release on failed cdev bind --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] vfio: premature KVM reference release on failed cdev bind
--
commit 3b919032adda2d9cc906ebc24555fad4223ad7c1
Author: Steffen Eiden <[email protected]>
KVM/vfio: Use file-based reference counting for KVM
Replace manual module reference counting with file-based reference counting for KVM integration. Pass struct file pointers instead of struct kvm pointers throughout the VFIO-KVM interface, leveraging the kernel's existing file reference counting.
Quoted 10 lines
drivers/vfio/vfio_main.c +0
-2@@ -511,14 +489,7 @@ void vfio_device_put_kvm(struct vfio_device *device)if (!device->kvm)return ; -if (WARN_ON (!device->put_kvm)) - goto clear;
drivers/vfio/vfio_main.c void vfio_device_put_kvm(struct vfio_device *device) · line 495
Quoted 10 lines
drivers/vfio/vfio_main.c +1
-6- - device->put_kvm (device->kvm); - device->put_kvm =NULL ; -symbol_put (kvm_put_kvm); - -clear: +fput (device->kvm); device->kvm = NULL; }#endif
[Severity: High]
Does this code introduce a regression where the globally shared device->kvm reference is prematurely released on a failed cdev bind?
Looking at drivers/vfio/device_cdev.c:vfio_df_ioctl_bind_iommufd(), if a secondary cdev open attempts to bind, it can fail because device->open_count != 0:
vfio_df_open(df); if (ret) goto out_put_kvm; ... out_put_kvm: vfio_device_put_kvm(device);
Unlike the group path which checks if device->open_count == 0, this cdev error path unconditionally drops the device->kvm reference via vfio_device_put_kvm(device). This drops the reference belonging to the primary opener and clears the pointer to NULL.
When the primary opener eventually accesses KVM (or when it closes the device), the driver skips unregistering the device from KVM, or relies on a raw pointer (e.g., matrix_mdev->kvm) to a KVM VM that may have already been freed. Can this regression result in a use-after-free?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 5/16 KVM: Restrict kvm_get_kvm/kvm_put_kvm export to internal KVM modules
In reply to Aneesh Kumar K.V (Arm)From: Steffen Eiden <[email protected]>
Switch kvm_get_kvm, kvm_get_kvm_safe, and kvm_put_kvm from EXPORT_SYMBOL_GPL to EXPORT_SYMBOL_FOR_KVM_INTERNAL. These symbols are now only used within KVM itself. No external module needs them now that VFIO uses file-based reference counting. Remove the corresponding exemptions from the x86 KVM Makefile exports check.
--- arch/x86/kvm/Makefile | 5 +---- virt/kvm/kvm_main.c | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-)
arch/x86/kvm/Makefile +1 -4
@@ -61,10 +61,7 @@ exports_grep_trailer := --include='*.[ch]' -nrw $(srctree)/virt/kvm $(srctree)/a -e kvm_page_track_unregister_notifier \ -e kvm_write_track_add_gfn \ -e kvm_write_track_remove_gfn \ - -e kvm_file_to_kvm_fn \ - -e kvm_get_kvm \ - -e kvm_get_kvm_safe \ - -e kvm_put_kvm + -e kvm_file_to_kvm_fn # Force grep to emit a goofy group separator that can in turn be replaced with # the above newline macro (newlines in Make are a nightmare). Note, grep only
virt/kvm/kvm_main.c +3 -3
@@ -1314,7 +1314,7 @@ void kvm_get_kvm(struct kvm *kvm) {refcount_inc (&kvm->users_count); } -EXPORT_SYMBOL_GPL (kvm_get_kvm); +EXPORT_SYMBOL_FOR_KVM_INTERNAL (kvm_get_kvm);/* * Make sure the vm is not during destruction, which is a safe version of
@@ -1324,14 +1324,14 @@ bool kvm_get_kvm_safe(struct kvm *kvm) {return refcount_inc_not_zero (&kvm->users_count); } -EXPORT_SYMBOL_GPL (kvm_get_kvm_safe); +EXPORT_SYMBOL_FOR_KVM_INTERNAL (kvm_get_kvm_safe);void kvm_put_kvm (struct kvm *kvm) {if (refcount_dec_and_test (&kvm->users_count))kvm_destroy_vm (kvm); } -EXPORT_SYMBOL_GPL (kvm_put_kvm); +EXPORT_SYMBOL_FOR_KVM_INTERNAL (kvm_put_kvm);/* * Used to put a reference that was taken on behalf of an object associated
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 05/16] KVM: Restrict kvm_get_kvm/kvm_put_kvm export to internal KVM modules
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines
From: Steffen Eiden <[email protected]>
Switch
kvm_get_kvm,kvm_get_kvm_safe, andkvm_put_kvmfromEXPORT_SYMBOL_GPLtoEXPORT_SYMBOL_FOR_KVM_INTERNAL. These symbols are now only used within KVM itself. No external module needs them now that VFIO uses file-based reference counting. Remove the corresponding exemptions from the x86 KVMMakefileexports check.Suggested-by Sean Christopherson <[email protected]>Signed-off-by Steffen Eiden <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 6/16 KVM: Remove unused file_is_kvm
In reply to Aneesh Kumar K.V (Arm)From: Steffen Eiden <[email protected]>
Remove the now unused file_is_kvm function.
--- include/linux/kvm_host.h | 1 - virt/kvm/kvm_main.c | 6 ------ 2 files changed, 7 deletions(-)
include/linux/kvm_host.h +0 -1
@@ -1087,7 +1087,6 @@ void kvm_exit(void);void kvm_get_kvm (struct kvm *kvm);bool kvm_get_kvm_safe (struct kvm *kvm);void kvm_put_kvm (struct kvm *kvm); -bool file_is_kvm (struct file *file);/* * Architectures define kvm_file_to_kvm_arch to <arch>
virt/kvm/kvm_main.c +0 -6
@@ -5515,12 +5515,6 @@ static struct file_operations kvm_vm_fops = { KVM_COMPAT(kvm_vm_compat_ioctl), }; -bool file_is_kvm (struct file *file) -{ -return file && file->f_op == &kvm_vm_fops; -} -EXPORT_SYMBOL_FOR_KVM_INTERNAL (file_is_kvm); -#ifdef kvm_file_to_kvm_archstruct kvm *kvm_file_to_kvm_fn (struct file *file) {
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 06/16] KVM: Remove unused file_is_kvm
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines
From: Steffen Eiden <[email protected]>
Remove the now unused
file_is_kvmfunction.Signed-off-by Steffen Eiden <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 7/16 iommufd/device: Associate KVM file pointer with iommufd_device
In reply to Aneesh Kumar K.V (Arm)From: Shameer Kolothum <[email protected]>
TSM vDevice support needs access to the KVM associated with a VFIO device after the device has been bound to iommufd.
Extend iommufd_device_bind() to accept the device's KVM file and store it in the iommufd_device. The KVM file reference is owned by VFIO and is already held for the duration of the device open path.
--- drivers/iommu/iommufd/device.c | 7 ++++++- drivers/iommu/iommufd/iommufd_private.h | 2 ++ drivers/iommu/iommufd/selftest.c | 2 +- drivers/vfio/iommufd.c | 3 ++- include/linux/iommufd.h | 4 +++- 5 files changed, 14 insertions(+), 4 deletions(-)
drivers/iommu/iommufd/device.c +6 -1
@@ -295,6 +295,7 @@ static int iommufd_bind_noiommu(struct iommufd_device *idev)* iommufd_device_bind - Bind a physical device to an iommu fd * @ictx: iommufd file descriptor * @dev: Pointer to a physical device struct +* @kvm_file: VM file if device belongs to a KVM VM * @id: Output ID number to return to userspace for this device * * A successful bind establishes an ownership over the device and returns
@@ -308,7 +309,9 @@ static int iommufd_bind_noiommu(struct iommufd_device *idev)* The caller must undo this with iommufd_device_unbind() */ struct iommufd_device *iommufd_device_bind (struct iommufd_ctx *ictx, -struct device *dev,u32 *id) +struct device *dev, +struct file *kvm_file, +u32 *id) {struct iommufd_device *idev;int rc;
@@ -319,6 +322,8 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, idev->ictx = ictx; idev->dev = dev; +/* VFIO holds the file reference while the device is open. */ + idev->kvm_file = kvm_file;if (!iommufd_device_is_noiommu (idev)) rc =iommufd_bind_iommu (idev);
drivers/iommu/iommufd/iommufd_private.h +2 -0
@@ -509,6 +509,8 @@ struct iommufd_device {struct list_head group_item;/* always the physical device */ struct device *dev; +/* ..and the VM file if available */ +struct file *kvm_file;bool enforce_cache_coherency;struct iommufd_vdevice *vdev;bool destroying;
drivers/iommu/iommufd/selftest.c +1 -1
@@ -1074,7 +1074,7 @@ static int iommufd_test_mock_domain(struct iommufd_ucmd *ucmd, goto out_sobj; } - idev =iommufd_device_bind (ucmd->ictx, &sobj->idev.mock_dev->dev, + idev =iommufd_device_bind (ucmd->ictx, &sobj->idev.mock_dev->dev,NULL , &idev_id);if (IS_ERR (idev)) { rc =PTR_ERR (idev);
drivers/vfio/iommufd.c +2 -1
@@ -123,7 +123,8 @@ int vfio_iommufd_physical_bind(struct vfio_device *vdev, {struct iommufd_device *idev; - idev =iommufd_device_bind (ictx, vdev->dev, out_device_id); + idev =iommufd_device_bind (ictx, vdev->dev, vdev->kvm, + out_device_id);if (IS_ERR (idev))return PTR_ERR (idev); vdev->iommufd_device = idev;
include/linux/iommufd.h +3 -1
@@ -59,7 +59,9 @@ struct iommufd_object { };struct iommufd_device *iommufd_device_bind (struct iommufd_ctx *ictx, -struct device *dev,u32 *id); +struct device *dev, +struct file *kvm_file, +u32 *id);void iommufd_device_unbind (struct iommufd_device *idev); int iommufd_device_attach(struct iommufd_device *idev, ioasid_t pasid,
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 07/16] iommufd/device: Associate KVM file pointer with iommufd_device
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] iommufd: UAF of kvm_file due to iommufd_device outliving VFIO unbind --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] iommufd: UAF of kvm_file due to iommufd_device outliving VFIO unbind --
commit ddde31c06624779bad69fb77ae7b9503b553dacc
Author: Shameer Kolothum <[email protected]>
iommufd/device: Associate KVM file pointer with iommufd_device
TSM vDevice support needs access to the KVM associated with a VFIO device after the device has been bound to iommufd. Extend iommufd_device_bind() to accept the device's KVM file and store it in the iommufd_device.
Quoted 4 lines
drivers/iommu/iommufd/device.c +0
-0
[ ... ]
Quoted 12 lines
drivers/iommu/iommufd/device.c +3
-1@@ -308,7 +309,9 @@ static int iommufd_bind_noiommu(struct iommufd_device *idev)* The caller must undo this with iommufd_device_unbind() */ struct iommufd_device *iommufd_device_bind (struct iommufd_ctx *ictx, -struct device *dev,u32 *id) +struct device *dev, +struct file *kvm_file, +u32 *id) {struct iommufd_device *idev;int rc;
drivers/iommu/iommufd/device.c struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, · line 326
Quoted 5 lines
drivers/iommu/iommufd/device.c +2
-0@@ -319,6 +322,8 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, idev->ictx = ictx; idev->dev = dev; +/* VFIO holds the file reference while the device is open. */ + idev->kvm_file = kvm_file;
[Severity: High]
Does this code introduce a use-after-free regression for kvm_file when a concurrent ioctl is accessing the device?
If an untrusted user thread executes an ioctl that calls iommufd_get_device(), it will increment idev->obj.users to 2.
If another thread concurrently closes the VFIO device fd, it calls vfio_df_unbind_iommufd() which leads to iommufd_device_unbind() and then iommufd_object_destroy_user().
Because users is 2, iommufd_object_remove() will return -EBUSY:
drivers/iommu/iommufd/main.c:iommufd_object_remove() {
...
if (!refcount_dec_if_one(&obj->users)) {
ret = -EBUSY;
goto err_xa;
}
...
}The unbind operation ignores this error, leaving the idev alive:
drivers/iommu/iommufd/iommufd_private.h:iommufd_object_destroy_user() {
...
ret = iommufd_object_remove(ictx, obj, obj->id, REMOVE_WAIT);
WARN_ON(ret);
}The VFIO device then drops its KVM reference during the close process, freeing the kvm_file. When the first thread resumes, will it access the freed kvm_file stored here?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
Aneesh Kumar K.V
maintainerRe: [PATCH v7 07/16] iommufd/device: Associate KVM file pointer with iommufd_device
Quoted 66 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] iommufd: UAF ofkvm_filedue toiommufd_deviceoutliving VFIO unbind --commit
ddde31c06624779bad69fb77ae7b9503b553dacc
Author: Shameer Kolothum <[email protected]>iommufd/device: Associate KVM file pointer with
iommufd_deviceTSM vDevice support needs access to the KVM associated with a VFIO device after the device has been bound to iommufd. Extend
iommufd_device_bind()to accept the device's KVM file and store it in theiommufd_device.Quoted 4 lines
drivers/iommu/iommufd/device.c +0
-0[ ... ]
Quoted 12 lines
drivers/iommu/iommufd/device.c +3
-1@@ -308,7 +309,9 @@ static int iommufd_bind_noiommu(struct iommufd_device *idev)* The caller must undo this with iommufd_device_unbind() */ struct iommufd_device *iommufd_device_bind (struct iommufd_ctx *ictx, -struct device *dev,u32 *id) +struct device *dev, +struct file *kvm_file, +u32 *id) {struct iommufd_device *idev;int rc;drivers/iommu/iommufd/device.c struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, · line 326
Quoted 5 lines
drivers/iommu/iommufd/device.c +2
-0@@ -319,6 +322,8 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, idev->ictx = ictx; idev->dev = dev; +/* VFIO holds the file reference while the device is open. */ + idev->kvm_file = kvm_file;[Severity: High]
Does this code introduce a use-after-free regression forkvm_filewhen a concurrent ioctl is accessing the device?If an untrusted user thread executes an ioctl that calls
iommufd_get_device(), it will incrementidev->obj.usersto 2.If another thread concurrently closes the VFIO device fd, it calls
vfio_df_unbind_iommufd()which leads toiommufd_device_unbind()and theniommufd_object_destroy_user().Because users is 2,
iommufd_object_remove()will return-EBUSY:drivers/iommu/iommufd/main.c:iommufd_object_remove() { ... if (!refcount_dec_if_one(&obj->users)) { ret = -EBUSY; goto err_xa; } ... }The unbind operation ignores this error, leaving the idev alive:
drivers/iommu/iommufd/iommufd_private.h:iommufd_object_destroy_user() { ... ret = iommufd_object_remove(ictx, obj, obj->id, REMOVE_WAIT); WARN_ON(ret); }
Quoted 4 lines
The VFIO device then drops its KVM reference during the close process, freeing the
kvm_file. When the first thread resumes, will it access the freedkvm_filestored here?
This is the VFIO device fd close stack:
vfio_device_fops_release()
vfio_df_unbind_iommufd()
vfio_df_close()
vfio_df_device_last_close()
vfio_df_iommufd_unbind()
vfio_iommufd_physical_unbind()
iommufd_device_unbind()
iommufd_object_destroy_user()
iommufd_object_remove(..., REMOVE_WAIT)
iommufd_object_dec_wait() /* timeout: -EBUSY */
WARN_ON(ret)
vfio_device_put_kvm()
fput(device->kvm)IIUC, this is a real issue: in this case, we neither remove the idev object from the xarray nor invoke its destroy callback. Is that correct?
After the REMOVE_WAIT timeout, unbind returns without removing the device from the xarray. VFIO close then continues, but the final ioctl put by the thread that called iommufd_get_device() only decrements users and wait_cnt; it does not retry destruction. The surviving idev also holds an iommufd file reference, so closing the userspace iommufd descriptor cannot complete the cleanup.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 07/16] iommufd/device: Associate KVM file pointer with iommufd_device
Quoted 60 lines · Aneesh Kumar K.V
Quoted 58 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] iommufd: UAF ofkvm_filedue toiommufd_deviceoutliving VFIO unbind --commit
ddde31c06624779bad69fb77ae7b9503b553dacc
Author: Shameer Kolothum <[email protected]>iommufd/device: Associate KVM file pointer with
iommufd_deviceTSM vDevice support needs access to the KVM associated with a VFIO device after the device has been bound to iommufd. Extend
iommufd_device_bind()to accept the device's KVM file and store it in theiommufd_device.Quoted 4 lines
drivers/iommu/iommufd/device.c +0
-0[ ... ]
Quoted 12 lines
drivers/iommu/iommufd/device.c +3
-1@@ -308,7 +309,9 @@ static int iommufd_bind_noiommu(struct iommufd_device *idev)* The caller must undo this with iommufd_device_unbind() */ struct iommufd_device *iommufd_device_bind (struct iommufd_ctx *ictx, -struct device *dev,u32 *id) +struct device *dev, +struct file *kvm_file, +u32 *id) {struct iommufd_device *idev;int rc;drivers/iommu/iommufd/device.c struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, · line 326
Quoted 5 lines
drivers/iommu/iommufd/device.c +2
-0@@ -319,6 +322,8 @@ struct iommufd_device *iommufd_device_bind(struct iommufd_ctx *ictx, idev->ictx = ictx; idev->dev = dev; +/* VFIO holds the file reference while the device is open. */ + idev->kvm_file = kvm_file;[Severity: High]
Does this code introduce a use-after-free regression forkvm_filewhen a concurrent ioctl is accessing the device?If an untrusted user thread executes an ioctl that calls
iommufd_get_device(), it will incrementidev->obj.usersto 2.If another thread concurrently closes the VFIO device fd, it calls
vfio_df_unbind_iommufd()which leads toiommufd_device_unbind()and theniommufd_object_destroy_user().Because users is 2,
iommufd_object_remove()will return-EBUSY:drivers/iommu/iommufd/main.c:iommufd_object_remove() { ... if (!refcount_dec_if_one(&obj->users)) { ret = -EBUSY; goto err_xa; } ... }
Quoted 6 lines
Quoted 6 lines
The unbind operation ignores this error, leaving the idev alive:
drivers/iommu/iommufd/iommufd_private.h:iommufd_object_destroy_user() { ... ret = iommufd_object_remove(ictx, obj, obj->id, REMOVE_WAIT); WARN_ON(ret);
REMOVE_WAIT is not supposed to fail, that is why there is a WARN_ON. If we hit a bug which causes this you get a warn on and other stuff like UAFs too.
Don't worry about it.
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 8/16 iommufd/viommu: Keep a reference to the KVM file
In reply to Aneesh Kumar K.V (Arm)From: Nicolin Chen <[email protected]>
The TSM vDevice operations need access to the KVM associated with the device's vIOMMU. Save the device's KVM file in the iommufd_viommu when the vIOMMU is allocated, and take a file reference so it remains valid for the lifetime of the vIOMMU.
Release the reference when the vIOMMU is destroyed.
Based on an original patch by
Shameer Kolothum <[email protected]>
--- drivers/iommu/iommufd/viommu.c | 5 +++++ include/linux/iommufd.h | 1 + 2 files changed, 6 insertions(+)
drivers/iommu/iommufd/viommu.c +5 -0
@@ -1,6 +1,7 @@// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES */ +#include <linux/file.h> #include "iommufd_private.h" void iommufd_viommu_destroy (struct iommufd_object *obj)
@@ -11,6 +12,8 @@ void iommufd_viommu_destroy(struct iommufd_object *obj)if (viommu->ops && viommu->ops->destroy) viommu->ops->destroy (viommu);refcount_dec (&viommu->hwpt->common.obj.users); +if (viommu->kvm_file) +fput (viommu->kvm_file);xa_destroy (&viommu->vdevs); }
@@ -82,6 +85,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) }xa_init (&viommu->vdevs); +if (idev->kvm_file) + viommu->kvm_file =get_file (idev->kvm_file); viommu->type = cmd->type; viommu->ictx = ucmd->ictx; viommu->hwpt = hwpt_paging;
include/linux/iommufd.h +1 -0
@@ -103,6 +103,7 @@ struct iommufd_viommu {struct iommufd_ctx *ictx;struct iommu_device *iommu_dev;struct iommufd_hwpt_paging *hwpt; +struct file *kvm_file;const struct iommufd_viommu_ops *ops;
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 08/16] iommufd/viommu: Keep a reference to the KVM file
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 12 lines
From: Nicolin Chen <[email protected]>
The TSM vDevice operations need access to the KVM associated with the device's vIOMMU. Save the device's KVM file in the
iommufd_viommuwhen the vIOMMU is allocated, and take a file reference so it remains valid for the lifetime of the vIOMMU.Release the reference when the vIOMMU is destroyed.
Based on an original patch by
Shameer Kolothum <[email protected]>
Quoted 9 lines
[nicolinc: hold kvm's users_count]Signed-off-by Nicolin Chen <[email protected]>[aneesh.kumar: Switch to use kvm_file]Signed-off-by Aneesh Kumar K.V (Arm) <[email protected]>
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
Re: [PATCH v7 08/16] iommufd/viommu: Keep a reference to the KVM file
In reply to Aneesh Kumar K.V (Arm)Hi Aneesh,
Quoted 48 lines · On Thu, 8 Oct 2026 11:29:47 +0530 "Aneesh Kumar K.V (Arm)"…
From: Nicolin Chen <[email protected]>
The TSM vDevice operations need access to the KVM associated with the device's vIOMMU. Save the device's KVM file in the
iommufd_viommuwhen the vIOMMU is allocated, and take a file reference so it remains valid for the lifetime of the vIOMMU.Release the reference when the vIOMMU is destroyed.
Based on an original patch by
Shameer Kolothum <[email protected]>[nicolinc: hold kvm's users_count]Signed-off-by Nicolin Chen <[email protected]>[aneesh.kumar: Switch to use kvm_file]Signed-off-by Aneesh Kumar K.V (Arm) <[email protected]>--- drivers/iommu/iommufd/viommu.c | 5 +++++ include/linux/iommufd.h | 1 + 2 files changed, 6 insertions(+)(quoted diff) +0
-0
b/drivers/iommu/iommufd/viommu.cindexf7951057a1e5..b7489ed259bd100644 ---a/drivers/iommu/iommufd/viommu.c
+++b/drivers/iommu/iommufd/viommu.cdrivers/iommu/iommufd/viommu.c +3
-0@@ -1,6 +1,7 @@// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES */ +#include <linux/file.h> #include "iommufd_private.h" void iommufd_viommu_destroy (struct iommufd_object *obj)@@ -11,6 +12,8 @@ void iommufd_viommu_destroy(struct iommufd_object *obj) if (viommu->ops && viommu->ops->destroy) viommu->ops->destroy (viommu);refcount_dec (&viommu->hwpt->common.obj.users); +if (viommu->kvm_file) +fput (viommu->kvm_file);xa_destroy (&viommu->vdevs); }
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd · line 89
Quoted 5 lines
drivers/iommu/iommufd/viommu.c +2
-0@@ -82,6 +85,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) }xa_init (&viommu->vdevs); +if (idev->kvm_file) + viommu->kvm_file =get_file (idev->kvm_file);
So kvm_file will persist inside viommu even when no more vdevice exists under the viommu. In that case, don't we need to check idev->kvm_file matches viommu->kvm_file during vdevice allocation?
Otherwise, it appears possible to associate a device belonging to a different KVM VM with the existing vIOMMU, no?
Quoted 15 lines
drivers/iommu/iommufd/viommu.c +0
-0viommu->type = cmd->type; viommu->ictx = ucmd->ictx; viommu->hwpt = hwpt_paging;include/linux/iommufd.h +1
-0@@ -103,6 +103,7 @@ struct iommufd_viommu {struct iommufd_ctx *ictx;struct iommu_device *iommu_dev;struct iommufd_hwpt_paging *hwpt; +struct file *kvm_file;const struct iommufd_viommu_ops *ops;
Aneesh Kumar K.V
maintainerRe: [PATCH v7 08/16] iommufd/viommu: Keep a reference to the KVM file
Quoted 62 lines · Jacob Pan
Hi Aneesh,
Quoted 48 lines · On Thu, 8 Oct 2026 11:29:47 +0530 "Aneesh Kumar K.V (Arm)"…
From: Nicolin Chen <[email protected]>
The TSM vDevice operations need access to the KVM associated with the device's vIOMMU. Save the device's KVM file in the
iommufd_viommuwhen the vIOMMU is allocated, and take a file reference so it remains valid for the lifetime of the vIOMMU.Release the reference when the vIOMMU is destroyed.
Based on an original patch by
Shameer Kolothum <[email protected]>[nicolinc: hold kvm's users_count]Signed-off-by Nicolin Chen <[email protected]>[aneesh.kumar: Switch to use kvm_file]Signed-off-by Aneesh Kumar K.V (Arm) <[email protected]>--- drivers/iommu/iommufd/viommu.c | 5 +++++ include/linux/iommufd.h | 1 + 2 files changed, 6 insertions(+)(quoted diff) +0
-0
b/drivers/iommu/iommufd/viommu.cindexf7951057a1e5..b7489ed259bd100644 ---a/drivers/iommu/iommufd/viommu.c
+++b/drivers/iommu/iommufd/viommu.cdrivers/iommu/iommufd/viommu.c +3
-0@@ -1,6 +1,7 @@// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES */ +#include <linux/file.h> #include "iommufd_private.h" void iommufd_viommu_destroy (struct iommufd_object *obj)@@ -11,6 +12,8 @@ void iommufd_viommu_destroy(struct iommufd_object *obj) if (viommu->ops && viommu->ops->destroy) viommu->ops->destroy (viommu);refcount_dec (&viommu->hwpt->common.obj.users); +if (viommu->kvm_file) +fput (viommu->kvm_file);xa_destroy (&viommu->vdevs); }drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd · line 89
Quoted 5 lines
drivers/iommu/iommufd/viommu.c +2
-0@@ -82,6 +85,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) }xa_init (&viommu->vdevs); +if (idev->kvm_file) + viommu->kvm_file =get_file (idev->kvm_file);So
kvm_filewill persist inside viommu even when no more vdevice exists under the viommu. In that case, don't we need to checkidev->kvm_filematchesviommu->kvm_fileduring vdevice allocation?
Quoted 3 lines
Otherwise, it appears possible to associate a device belonging to a different KVM VM with the existing vIOMMU, no?
Agreed. I will add the below.
modified drivers/iommu/iommufd/viommu.c
@@ -212,6 +212,11 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
goto out_put_viommu;
}
+ if (idev->kvm_file != viommu->kvm_file) {
+ rc = -EINVAL;
+ goto out_put_idev;
+ }
+
if (viommu->iommu_dev) {
iommu_dev = iommufd_device_get_iommu_dev(idev); if (!iommu_dev || viommu->iommu_dev != iommu_dev) {
-aneesh
Tian, Kevin
maintainerRE: [PATCH v7 08/16] iommufd/viommu: Keep a reference to the KVM file
Quoted 29 lines
From: Aneesh Kumar K.V <[email protected]>
Sent: Thursday, October 29, 2026 2:26 PMQuoted 6 lines · Jacob Pan
Hi Aneesh,
Review int iommufd_viommu_alloc_ioctl(struct
Quoted 1 line · On Thu, 8 Oct 2026 11:29:47 +0530 "Aneesh Kumar K.V (Arm)"…
(quoted diff) +0
-0@@ -82,6 +85,8 @@ int iommufd_viommu_alloc_ioctl(struct
iommufd_ucmdQuoted 9 lines
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) · line 89
Quoted 5 lines
drivers/iommu/iommufd/viommu.c +2
-0*ucmd) }xa_init (&viommu->vdevs); +if (idev->kvm_file) + viommu->kvm_file =get_file (idev->kvm_file);So
kvm_filewill persist inside viommu even when no more vdevice exists under the viommu. In that case, don't we need to checkidev->kvm_filematchesviommu->kvm_fileduring vdevice allocation?Quoted 3 lines
Otherwise, it appears possible to associate a device belonging to a different KVM VM with the existing vIOMMU, no?
Agreed. I will add the below.
modified
drivers/iommu/iommufd/viommu.c
Review int iommufd_vdevice_alloc_ioctl(struct · line 220
Quoted 9 lines
(quoted diff) +5
-0@@ -212,6 +212,11 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd) goto out_put_viommu; } + if (idev->kvm_file != viommu->kvm_file) { + rc = -EINVAL; + goto out_put_idev; + } +
IMHO this is required beyond the tsm usage.
Currently IOMMUFD doesn't prevent bound devices tied to different VMs, but in concept a vIOMMU should not be shared by two VMs.
The uAPI header already says:
"
* Allocate a virtual IOMMU object, representing the underlying physical IOMMU's
* virtualization support that is a security-isolated slice of the real IOMMU HW
* that is unique to a specific VM.
"
But that uniqueness is not enforced so far, and your proposed change can fix it.
Besides, it might be a good idea to return an error if idev->kvm_file is NULL in iommufd_viommu_alloc_ioctl(). I don't think the viommu logic is designed for native application...
Re: [PATCH v7 08/16] iommufd/viommu: Keep a reference to the KVM file
Quoted 3 lines · Kevin
Besides, it might be a good idea to return an error if
idev->kvm_fileis NULL iniommufd_viommu_alloc_ioctl(). I don't think the viommu logic is designed for native application...
That's probably going too far..
It should be possible to fully test most of the viommu stuff without an actual kvm, for example.
Jason
Re: [PATCH v7 08/16] iommufd/viommu: Keep a reference to the KVM file
In reply to Aneesh Kumar K.V (Arm)Quoted 8 lines · Aneesh Kumar K.V (Arm)
The TSM vDevice operations need access to the KVM associated with the device's vIOMMU. Save the device's KVM file in the
iommufd_viommuwhen the vIOMMU is allocated, and take a file reference so it remains valid for the lifetime of the vIOMMU.Release the reference when the vIOMMU is destroyed.
[...]
Signature · 1 line
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 9/16 tsm: Remove the device from lookup before PCI teardown
In reply to Aneesh Kumar K.V (Arm)PCI connect looks up a TSM by ID under pci_tsm_rwsem. Previously, tsm_unregister() released that lock after tearing down PCI state while the TSM was still in the class lookup. A racing connect could then attach a new PCI context that the teardown would never see.
Remove the device from the class lookup first, then take the PCI write lock to drain in-flight operations and destroy their contexts. Drop the device reference only after PCI teardown completes.
--- drivers/virt/coco/tsm-core.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-)
drivers/virt/coco/tsm-core.c +14 -8
@@ -56,26 +56,25 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)return no_free_ptr (tsm_dev); } -static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {int rc;if (!pci_ops) -return tsm_dev; +return 0 ; tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;dev_err (tsm_dev->dev.parent,"PCI/TSM registration failure: %d\n" , rc); -device_unregister (&tsm_dev->dev); -return ERR_PTR (rc); +return rc; }/* Notify TSM userspace that PCI/TSM operations are now possible */ kobject_uevent (&tsm_dev->dev.kobj,KOBJ_CHANGE ); -return tsm_dev; +return 0 ; }struct tsm_dev *tsm_register (struct device *parent,struct pci_tsm_ops *pci_ops)
@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev); +return ERR_PTR (rc); + } +return no_free_ptr (tsm_dev); }EXPORT_SYMBOL_GPL (tsm_register);void tsm_unregister (struct tsm_dev *tsm_dev) { +/* Remove the class lookup first. */ +device_del (&tsm_dev->dev);if (tsm_dev->pci_ops)pci_tsm_unregister (tsm_dev); -device_unregister (&tsm_dev->dev); +put_device (&tsm_dev->dev); }EXPORT_SYMBOL_GPL (tsm_unregister);
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] tsm: race in tsm_register() error path leads to UAF and NULL pointer deref --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tsm: race in tsm_register() error path leads to UAF and NULL pointer deref --
commit f9b77b7469552ae6078c3a47333908a88eec2474
Author: Aneesh Kumar K.V (Arm) <[email protected]>
tsm: Remove the device from lookup before PCI teardown
This commit modifies the teardown flow to remove the TSM device from lookup before tearing down the PCI state. It also adjusts the setup flow in tsm_register() to handle PCI registration failures differently.
Quoted 4 lines
drivers/virt/coco/tsm-core.c +0
-0@@ -56,26 +56,25 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)
[ ... ]
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-2-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {
[ ... ]
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;
[Severity: Medium]
Does this unprotected write to tsm_dev->pci_ops introduce a race condition?
If pci_tsm_register() returns an error early (e.g. if a driver incorrectly provides both link_ops and devsec_ops, returning -EINVAL before acquiring pci_tsm_rwsem), a concurrent userspace write to the connect sysfs attribute might trigger connect_store().
If connect_store() evaluates is_link_tsm() concurrently, this lockless assignment to NULL on the error path could cause the compiler to reload the pointer after the NULL check. Could this lead to a NULL pointer dereference when accessing ->link_ops.probe?
Quoted 6 lines
drivers/virt/coco/tsm-core.c +1
-2dev_err (tsm_dev->dev.parent,"PCI/TSM registration failure: %d\n" , rc); -device_unregister (&tsm_dev->dev); -return ERR_PTR (rc); +return rc; }
[ ... ]
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 8 lines
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);
[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?
Since device_add() is called prior to tsm_register_pci() in tsm_register(), the device is already exposed via class_find_device(). If a concurrent connect_store() finds the device and proceeds to pci_tsm_connect() before this error path cleans it up, it will attach the PCI context to tsm_dev.
When this error path calls device_del() and returns, the __free(put_tsm_dev) guard at the top of tsm_register() will drop the primary reference to tsm_dev, freeing its memory.
Because the attached PCI context stores the pointer without taking an additional device reference, wouldn't this leave a dangling pointer in the PCI context, resulting in a use-after-free on subsequent accesses?
Quoted 4 lines
drivers/virt/coco/tsm-core.c +3
-0+return ERR_PTR (rc); + } +return no_free_ptr (tsm_dev); }
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
Aneesh Kumar K.V
maintainerRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
Quoted 69 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tsm: race intsm_register()error path leads to UAF and NULL pointer deref --commit
f9b77b7469552ae6078c3a47333908a88eec2474
Author: Aneesh Kumar K.V (Arm) <[email protected]>tsm: Remove the device from lookup before PCI teardown
This commit modifies the teardown flow to remove the TSM device from lookup before tearing down the PCI state. It also adjusts the setup flow in
tsm_register()to handle PCI registration failures differently.Quoted 4 lines
drivers/virt/coco/tsm-core.c +0
-0@@ -56,26 +56,25 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)[ ... ]
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-2-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {[ ... ]
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;[Severity: Medium]
Does this unprotected write totsm_dev->pci_opsintroduce a race condition?If
pci_tsm_register()returns an error early (e.g. if a driver incorrectly provides bothlink_opsanddevsec_ops, returning-EINVALbefore acquiringpci_tsm_rwsem), a concurrent userspace write to the connect sysfs attribute might triggerconnect_store().If
connect_store()evaluatesis_link_tsm()concurrently, this lockless assignment to NULL on the error path could cause the compiler to reload the pointer after the NULL check. Could this lead to a NULL pointer dereference when accessing ->link_ops.probe?Quoted 6 lines
drivers/virt/coco/tsm-core.c +1
-2dev_err (tsm_dev->dev.parent,"PCI/TSM registration failure: %d\n" , rc); -device_unregister (&tsm_dev->dev); -return ERR_PTR (rc); +return rc; }[ ... ]
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 8 lines
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?Since
device_add()is called prior totsm_register_pci()intsm_register(), the device is already exposed viaclass_find_device(). If a concurrentconnect_store()finds the device and proceeds topci_tsm_connect()before this error path cleans it up, it will attach the PCI context totsm_dev.When this error path calls
device_del()and returns, the__free(put_tsm_dev)guard at the top oftsm_register()will drop the primary reference totsm_dev, freeing its memory.
Quoted 8 lines
Because the attached PCI context stores the pointer without taking an additional device reference, wouldn't this leave a dangling pointer in the PCI context, resulting in a use-after-free on subsequent accesses?
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 104
Quoted 4 lines
drivers/virt/coco/tsm-core.c +3
-0+return ERR_PTR (rc); + } +return no_free_ptr (tsm_dev); }
I think this problem already exists upstream. We should delay device_add() until PCI/TSM registration succeeds. The kobject event should also be emitted after device_add(). I will add the fix as a separate patch.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
Quoted 75 lines · Aneesh Kumar K.V
Quoted 73 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tsm: race intsm_register()error path leads to UAF and NULL pointer deref --commit
f9b77b7469552ae6078c3a47333908a88eec2474
Author: Aneesh Kumar K.V (Arm) <[email protected]>tsm: Remove the device from lookup before PCI teardown
This commit modifies the teardown flow to remove the TSM device from lookup before tearing down the PCI state. It also adjusts the setup flow in
tsm_register()to handle PCI registration failures differently.Quoted 4 lines
drivers/virt/coco/tsm-core.c +0
-0@@ -56,26 +56,25 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)[ ... ]
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-2-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {[ ... ]
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;[Severity: Medium]
Does this unprotected write totsm_dev->pci_opsintroduce a race condition?If
pci_tsm_register()returns an error early (e.g. if a driver incorrectly provides bothlink_opsanddevsec_ops, returning-EINVALbefore acquiringpci_tsm_rwsem), a concurrent userspace write to the connect sysfs attribute might triggerconnect_store().If
connect_store()evaluatesis_link_tsm()concurrently, this lockless assignment to NULL on the error path could cause the compiler to reload the pointer after the NULL check. Could this lead to a NULL pointer dereference when accessing ->link_ops.probe?Quoted 6 lines
drivers/virt/coco/tsm-core.c +1
-2dev_err (tsm_dev->dev.parent,"PCI/TSM registration failure: %d\n" , rc); -device_unregister (&tsm_dev->dev); -return ERR_PTR (rc); +return rc; }[ ... ]
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 8 lines
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?Since
device_add()is called prior totsm_register_pci()intsm_register(), the device is already exposed viaclass_find_device(). If a concurrentconnect_store()finds the device and proceeds topci_tsm_connect()before this error path cleans it up, it will attach the PCI context totsm_dev.When this error path calls
device_del()and returns, the__free(put_tsm_dev)guard at the top oftsm_register()will drop the primary reference totsm_dev, freeing its memory.Because the attached PCI context stores the pointer without taking an additional device reference, wouldn't this leave a dangling pointer in the PCI context, resulting in a use-after-free on subsequent accesses?
Quoted 10 lines
Quoted 4 lines
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 104
Quoted 4 lines
drivers/virt/coco/tsm-core.c +3
-0+return ERR_PTR (rc); + } +return no_free_ptr (tsm_dev); }I think this problem already exists upstream. We should delay
device_add()until PCI/TSM registration succeeds. The kobject event should also be emitted afterdevice_add(). I will add the fix as a separate patch.
Sigh, trying to do all this stuff through sysfs makes such an mess.. Nothing gets to be atomic in sysfs land so..
It looks like this ordering is to ensure that when connect appears on the pci device it can immediately be used with the tsm that triggered it's creation. I'm not sure this is valuable but I haven't worked out how a userspace is even supposed to exist with all these troublesome races.
If you swap the order and remove the extra events then userspace can see a connect sysfs but no tsm0 device. I think this is possibly better as userspace will want to obtain the tsm handle then process all the devices affiliated with it. So waiting around for a tsm and then knowning that every connect is already there strikes me as easier to work with. IDK
The bigger issue here is I have no idea how the userspace is supposed to work. When my imagined 'systemd device accept d' starts it will see a sytem with no TSM and no connect sysfs.
A tsm may eventually appear when the TSM module gets loaded, eg it loads by udev through the smccc bus on CCA.
Or it might need manual module loading because it is a SW DOE based tsm.
Or it might never ever appear.
So what is the device-accept-d logic supposed to even look like?
Before touching this we need to come up with a draft systemd-device-accept-d shaped thing that does the TSM startup before drivers bind. Then we can see fully how it can work. I asked someone to start working on this, but I expected there to be a ready story on how it should work, not more design work.
One possible answer is more TSM stuff has to be built in so we don't hotplug the default TSM. Then the userspace simply checks for a TSM, if present it connects it, otherwise maybe it thinks about using the DOE one just to get an attestation log.
Module auto loading looks troublesome too, if we want to use the SW based DOE TSM then how does device-accept-d trigger the module load when there isn't even any sysfs?
Once again I'm thinking if the sysfs as a mistake and wondering if it works better using a /dev/tsm.. :(
In any event, lets not get everything tangled into everything else. This series is about viommu, lets keep track of these other issues and keep focusing on viommu..
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
Quoted 87 lines · Jason Gunthorpe
Quoted 75 lines · Aneesh Kumar K.V
Quoted 73 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tsm: race intsm_register()error path leads to UAF and NULL pointer deref --commit
f9b77b7469552ae6078c3a47333908a88eec2474
Author: Aneesh Kumar K.V (Arm) <[email protected]>tsm: Remove the device from lookup before PCI teardown
This commit modifies the teardown flow to remove the TSM device from lookup before tearing down the PCI state. It also adjusts the setup flow in
tsm_register()to handle PCI registration failures differently.Quoted 4 lines
drivers/virt/coco/tsm-core.c +0
-0@@ -56,26 +56,25 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)[ ... ]
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-2-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {[ ... ]
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;[Severity: Medium]
Does this unprotected write totsm_dev->pci_opsintroduce a race condition?If
pci_tsm_register()returns an error early (e.g. if a driver incorrectly provides bothlink_opsanddevsec_ops, returning-EINVALbefore acquiringpci_tsm_rwsem), a concurrent userspace write to the connect sysfs attribute might triggerconnect_store().If
connect_store()evaluatesis_link_tsm()concurrently, this lockless assignment to NULL on the error path could cause the compiler to reload the pointer after the NULL check. Could this lead to a NULL pointer dereference when accessing ->link_ops.probe?Quoted 6 lines
drivers/virt/coco/tsm-core.c +1
-2dev_err (tsm_dev->dev.parent,"PCI/TSM registration failure: %d\n" , rc); -device_unregister (&tsm_dev->dev); -return ERR_PTR (rc); +return rc; }[ ... ]
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 8 lines
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?Since
device_add()is called prior totsm_register_pci()intsm_register(), the device is already exposed viaclass_find_device(). If a concurrentconnect_store()finds the device and proceeds topci_tsm_connect()before this error path cleans it up, it will attach the PCI context totsm_dev.When this error path calls
device_del()and returns, the__free(put_tsm_dev)guard at the top oftsm_register()will drop the primary reference totsm_dev, freeing its memory.Because the attached PCI context stores the pointer without taking an additional device reference, wouldn't this leave a dangling pointer in the PCI context, resulting in a use-after-free on subsequent accesses?
Quoted 10 lines
Quoted 4 lines
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 104
Quoted 4 lines
drivers/virt/coco/tsm-core.c +3
-0+return ERR_PTR (rc); + } +return no_free_ptr (tsm_dev); }I think this problem already exists upstream. We should delay
device_add()until PCI/TSM registration succeeds. The kobject event should also be emitted afterdevice_add(). I will add the fix as a separate patch.
Quoted 9 lines
Sigh, trying to do all this stuff through sysfs makes such an mess.. Nothing gets to be atomic in sysfs land so..
It looks like this ordering is to ensure that when connect appears on the pci device it can immediately be used with the tsm that triggered it's creation. I'm not sure this is valuable but I haven't worked out how a userspace is even supposed to exist with all these troublesome races.
The issue is theoretical and does not arise with a single link TSM driver. The PCI device's "connect" attribute is added only after pci_tsm_register() succeeds.
The race can occur when registering a second TSM. The "connect" attribute already exists, and userspace may try to connect using the new link driver, for example "tsm1". connect_store() then looks up ID 1 and can find the new tsm_dev in tsm_class because device_add() has already completed.
The proposed fix is to reorder registration so that device_add() is called last.
ie, changes the sequence from:
rc = dev_set_name(dev, "tsm%d", tsm_dev->id); if (rc) return ERR_PTR(rc); rc = device_add(dev); if (rc) return ERR_PTR(rc); return tsm_register_pci_or_reset(no_free_ptr(tsm_dev), pci_ops);
to this.
rc = dev_set_name(dev, "tsm%d", tsm_dev->id);
if (rc)
return ERR_PTR(rc);
/* Complete PCI registration before exposing the TSM to class lookup. */
rc = tsm_register_pci(tsm_dev, pci_ops);
if (rc)
return ERR_PTR(rc);
rc = device_add(dev);
if (rc) {
if (pci_ops)
pci_tsm_unregister(tsm_dev);
return ERR_PTR(rc);
}
/* Notify TSM userspace that PCI/TSM operations are now possible. */
if (pci_ops)
kobject_uevent(&dev->kobj, KOBJ_CHANGE);Quoted 20 lines
If you swap the order and remove the extra events then userspace can see a connect sysfs but no tsm0 device. I think this is possibly better as userspace will want to obtain the tsm handle then process all the devices affiliated with it. So waiting around for a tsm and then knowning that every connect is already there strikes me as easier to work with. IDK
The bigger issue here is I have no idea how the userspace is supposed to work. When my imagined 'systemd device accept d' starts it will see a sytem with no TSM and no connect sysfs.
A tsm may eventually appear when the TSM module gets loaded, eg it loads by udev through the smccc bus on CCA.
Or it might need manual module loading because it is a SW DOE based tsm.
Or it might never ever appear.
Quoted 8 lines
So what is the device-accept-d logic supposed to even look like?
Before touching this we need to come up with a draft systemd-device-accept-d shaped thing that does the TSM startup before drivers bind. Then we can see fully how it can work. I asked someone to start working on this, but I expected there to be a ready story on how it should work, not more design work.
Agreed. It would be useful to understand the complete expected userspace flow.
This could also be a manual setup in which the administrator explicitly marks specific devices for IDE connection?
Quoted 10 lines
One possible answer is more TSM stuff has to be built in so we don't hotplug the default TSM. Then the userspace simply checks for a TSM, if present it connects it, otherwise maybe it thinks about using the DOE one just to get an attestation log.
Module auto loading looks troublesome too, if we want to use the SW based DOE TSM then how does device-accept-d trigger the module load when there isn't even any sysfs?
I expect these operations to require manual setup: the administrator would first unbind the device from its driver, configure IDE, and then bind it to vfio-pci if the device is to be passed through. Since device bind and unbind operations are already driven through sysfs, it seems natural for the connect operation to use sysfs as well?
Quoted 8 lines
Once again I'm thinking if the sysfs as a mistake and wondering if it works better using a
/dev/tsm.. :(In any event, lets not get everything tangled into everything else. This series is about viommu, lets keep track of these other issues and keep focusing on viommu..
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
Quoted 2 lines · Aneesh Kumar K.V
The proposed fix is to reorder registration so that
device_add()is called last.
IMHO I'd leave this until we have a complete story. I'm nervous it was written this way for some reason that still isn't obvious.
Quoted 3 lines
I expect these operations to require manual setup: the administrator would first unbind the device from its driver, configure IDE, and then bind it to vfio-pci if the device is to be passed through.
No, definitely not. Once a driver is bound that is a security decision that cannot be taken back. If this stuff is going to provide any security it must happen before the driver is bound. This is why the trust level exists to prevent driver binding so userspace can inject some measurement and policy.
In bare metal cases where you might want to use the SW DOE flow this still has to happen before binding any drivers, we can take a measurement of the device and log it before granting it any access to the system.
So the whole thing needs to work in some fairly automatic way where the device-accept-d in the initrd is starting up SPDM, making measurements, applying some policy, then binding drivers.
Meaning right at the very start it has to know if some kind of platform TSM is available and it has to be able to auto-load the SW DOE if there isn't..
And make decisions about which devices even have a TSM option or not.
Quoted 3 lines
Since device bind and unbind operations are already driven through sysfs, it seems natural for the connect operation to use sysfs as well?
Only if it can be made to actually work properly.
Jason
Re: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
In reply to Aneesh Kumar K.VQuoted 103 lines · Aneesh Kumar K.V
Quoted 87 lines · Jason Gunthorpe
Quoted 75 lines · Aneesh Kumar K.V
Quoted 73 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tsm: race intsm_register()error path leads to UAF and NULL pointer deref --commit
f9b77b7469552ae6078c3a47333908a88eec2474
Author: Aneesh Kumar K.V (Arm) <[email protected]>tsm: Remove the device from lookup before PCI teardown
This commit modifies the teardown flow to remove the TSM device from lookup before tearing down the PCI state. It also adjusts the setup flow in
tsm_register()to handle PCI registration failures differently.Quoted 4 lines
drivers/virt/coco/tsm-core.c +0
-0@@ -56,26 +56,25 @@ static struct tsm_dev *alloc_tsm_dev(struct device *parent)[ ... ]
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-2-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {[ ... ]
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 4 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;[Severity: Medium]
Does this unprotected write totsm_dev->pci_opsintroduce a race condition?If
pci_tsm_register()returns an error early (e.g. if a driver incorrectly provides bothlink_opsanddevsec_ops, returning-EINVALbefore acquiringpci_tsm_rwsem), a concurrent userspace write to the connect sysfs attribute might triggerconnect_store().If
connect_store()evaluatesis_link_tsm()concurrently, this lockless assignment to NULL on the error path could cause the compiler to reload the pointer after the NULL check. Could this lead to a NULL pointer dereference when accessing ->link_ops.probe?Quoted 6 lines
drivers/virt/coco/tsm-core.c +1
-2dev_err (tsm_dev->dev.parent,"PCI/TSM registration failure: %d\n" , rc); -device_unregister (&tsm_dev->dev); -return ERR_PTR (rc); +return rc; }[ ... ]
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 8 lines
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?Since
device_add()is called prior totsm_register_pci()intsm_register(), the device is already exposed viaclass_find_device(). If a concurrentconnect_store()finds the device and proceeds topci_tsm_connect()before this error path cleans it up, it will attach the PCI context totsm_dev.When this error path calls
device_del()and returns, the__free(put_tsm_dev)guard at the top oftsm_register()will drop the primary reference totsm_dev, freeing its memory.Because the attached PCI context stores the pointer without taking an additional device reference, wouldn't this leave a dangling pointer in the PCI context, resulting in a use-after-free on subsequent accesses?
Quoted 10 lines
Quoted 4 lines
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 104
Quoted 4 lines
drivers/virt/coco/tsm-core.c +3
-0+return ERR_PTR (rc); + } +return no_free_ptr (tsm_dev); }I think this problem already exists upstream. We should delay
device_add()until PCI/TSM registration succeeds. The kobject event should also be emitted afterdevice_add(). I will add the fix as a separate patch.Quoted 9 lines
Sigh, trying to do all this stuff through sysfs makes such an mess.. Nothing gets to be atomic in sysfs land so..
It looks like this ordering is to ensure that when connect appears on the pci device it can immediately be used with the tsm that triggered it's creation. I'm not sure this is valuable but I haven't worked out how a userspace is even supposed to exist with all these troublesome races.
The issue is theoretical and does not arise with a single link TSM driver. The PCI device's "connect" attribute is added only after
pci_tsm_register()succeeds.
Quoted 8 lines
The race can occur when registering a second TSM. The "connect" attribute already exists, and userspace may try to connect using the new link driver, for example "tsm1".
connect_store()then looks up ID 1 and can find the newtsm_devintsm_classbecausedevice_add()has already completed.The proposed fix is to reorder registration so that
device_add()is called last.
Yes, since the cleanup path is changed to:
device_del()
pci_tsm_unregister()
put_device()
the initialization patch should be:
device_initialize()
pci_tsm_register()
device_add()
Quoted 13 lines
ie, changes the sequence from:
rc = dev_set_name(dev, "tsm%d", tsm_dev->id); if (rc) return ERR_PTR(rc); rc = device_add(dev); if (rc) return ERR_PTR(rc); return tsm_register_pci_or_reset(no_free_ptr(tsm_dev), pci_ops);
Quoted 8 lines
to this.
rc = dev_set_name(dev, "tsm%d", tsm_dev->id); if (rc) return ERR_PTR(rc); /* Complete PCI registration before exposing the TSM to class lookup. */ rc = tsm_register_pci(tsm_dev, pci_ops);
Maybe we don't need the helper any more, open code pci_tsm_register(), align with tsm_unregister(), seems clearer to me.
Quoted 5 lines
if (rc)
return ERR_PTR(rc);
rc = device_add(dev);
if (rc) {Quoted 9 lines
if (pci_ops) pci_tsm_unregister(tsm_dev); return ERR_PTR(rc); } /* Notify TSM userspace that PCI/TSM operations are now possible. */ if (pci_ops) kobject_uevent(&dev->kobj, KOBJ_CHANGE);
I'm good to the analysis and good to the fix. I was confused why the original patch refactored tsm_register_pci_or_reset() with no functional change. But with this proposal, the change becomes reasonable.
I haven't finished the series yet and am not sure why this fix is needed in this series, how it affects the following patches. But it is a good fix standalone.
Jason Gunthorpe
maintainerRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
In reply to Aneesh Kumar K.V (Arm)drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 64
Quoted 10 lines
[ ... 22 lines skipped ... ]
drivers/virt/coco/tsm-core.c +2
-3-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {int rc;if (!pci_ops) -return tsm_dev; +return 0 ;
Aside unrelated to this series: Do we need this NULL pci_ops stuff? I don't know what it was imagined to do, but now that we have split things up attestation subsystem I wonder if it would ever be useful?
It is quite a maze of unused layers in here..
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 5 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;
[Severity: Medium]
Does this unprotected write to tsm_dev->pci_ops introduce a race condition?
And of course sashiko finds races in all this unused complexity...
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 9 lines
[ ... 14 lines skipped ... ]
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);
[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?
Since device_add() is called prior to tsm_register_pci() in tsm_register(), the device is already exposed via class_find_device(). If a concurrent connect_store() finds the device and proceeds to pci_tsm_connect() before this error path cleans it up, it will attach the PCI context to tsm_dev.
Fun. Usually device_add should be the last thing in any driver flow, not the first. There should not be any device_del on error paths.
Because of races like this...
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 113
Quoted 9 lines
[ ... 6 lines skipped ... ]
voidtsm_unregister(struct tsm_dev *tsm_dev)
{drivers/virt/coco/tsm-core.c +3
-1+/* Remove the class lookup first. */ +device_del (&tsm_dev->dev);if (tsm_dev->pci_ops)pci_tsm_unregister (tsm_dev); -device_unregister (&tsm_dev->dev); +put_device (&tsm_dev->dev);
This makes sense at least..
Signature · 1 line
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown
Quoted 15 lines · Jason Gunthorpe
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 64
Quoted 10 lines
[ ... 22 lines skipped ... ]
drivers/virt/coco/tsm-core.c +2
-3-static struct tsm_dev *tsm_register_pci_or_reset (struct tsm_dev *tsm_dev, -struct pci_tsm_ops *pci_ops) +static int tsm_register_pci (struct tsm_dev *tsm_dev,struct pci_tsm_ops *pci_ops) {int rc;if (!pci_ops) -return tsm_dev; +return 0 ;Aside unrelated to this series: Do we need this NULL
pci_opsstuff? I don't know what it was imagined to do, but now that we have split things up attestation subsystem I wonder if it would ever be useful?
Quoted 2 lines
It is quite a maze of unused layers in here..
Agreed. I'll remove it; we can always add it back if a use case arises.
Quoted 28 lines
drivers/virt/coco/tsm-core.c static struct tsm_dev *alloc_tsm_dev(struct device *parent) · line 69
Quoted 6 lines
drivers/virt/coco/tsm-core.c +1
-0tsm_dev->pci_ops = pci_ops; rc =pci_tsm_register (tsm_dev);if (rc) { + tsm_dev->pci_ops =NULL ;[Severity: Medium]
Does this unprotected write totsm_dev->pci_opsintroduce a race condition?And of course sashiko finds races in all this unused complexity...
drivers/virt/coco/tsm-core.c struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops) · line 100
Quoted 9 lines
[ ... 14 lines skipped ... ]
drivers/virt/coco/tsm-core.c +3
-1@@ -96,15 +95,22 @@ struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)if (rc)return ERR_PTR (rc); -return tsm_register_pci_or_reset (no_free_ptr (tsm_dev), pci_ops); + rc =tsm_register_pci (tsm_dev, pci_ops); +if (rc) { +device_del (dev);[Severity: Medium]
Can this error path lead to a use-after-free if PCI registration fails?Since
device_add()is called prior totsm_register_pci()intsm_register(), the device is already exposed viaclass_find_device(). If a concurrentconnect_store()finds the device and proceeds topci_tsm_connect()before this error path cleans it up, it will attach the PCI context totsm_dev.
Quoted 5 lines
Fun. Usually
device_addshould be the last thing in any driver flow, not the first. There should not be anydevice_delon error paths.Because of races like this...
I now have:
struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)
{
struct tsm_dev *tsm_dev __free(put_tsm_dev) = alloc_tsm_dev(parent);
struct device *dev;
int rc;
if (IS_ERR(tsm_dev))
return tsm_dev;
dev = &tsm_dev->dev;
rc = dev_set_name(dev, "tsm%d", tsm_dev->id);
if (rc)
return ERR_PTR(rc);
/* Complete PCI registration before exposing the TSM to class lookup. */
rc = tsm_register_pci(tsm_dev, pci_ops);
if (rc)
return ERR_PTR(rc);
rc = device_add(dev);
if (rc) {
if (pci_ops)
pci_tsm_unregister(tsm_dev);
return ERR_PTR(rc);
}
/* Notify TSM userspace that PCI/TSM operations are now possible. */
if (pci_ops)
kobject_uevent(&dev->kobj, KOBJ_CHANGE);
return no_free_ptr(tsm_dev);
}-aneesh
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 10/16 iommufd: Add the vdevice TSM request ioctl
In reply to Aneesh Kumar K.V (Arm)A VMM needs to forward guest-originated TSM commands to the TSM implementation managing an assigned device. The IOMMUFD vdevice identifies that device within its vIOMMU and provides the appropriate dispatch point.
Add IOMMU_VDEVICE_TSM_REQ to send an opaque request to a vdevice and optionally return a response. Define common operation and guest architecture identifiers for CCA, SEV and TDX requests.
Let vdevice providers declare the TVM architecture and supported operations in a u64 mask. Check these capabilities before dispatch. Define a one-past-last operation value and assert that every operation fits in the mask; reject out-of-range userspace values before indexing it.
The ioctl return value reports errors or the number of unused buffer bytes, while tsm_code carries the TSM-specific result. Use userspace pointers for the internal request and response buffers, and u32 lengths matching the UAPI. Reject lengths above INT_MAX so successful residues fit in the ioctl's int return value.
--- drivers/iommu/iommufd/Makefile | 2 + drivers/iommu/iommufd/iommufd_private.h | 8 +++ drivers/iommu/iommufd/main.c | 3 + drivers/iommu/iommufd/tsm.c | 80 ++++++++++++++++++++++++ drivers/iommu/iommufd/viommu.c | 3 + include/linux/iommufd.h | 9 +++ include/linux/tsm.h | 23 +++++++ include/uapi/linux/iommufd.h | 82 +++++++++++++++++++++++++ 8 files changed, 210 insertions(+) create mode 100644 drivers/iommu/iommufd/tsm.c
drivers/iommu/iommufd/Makefile +2 -0
@@ -11,6 +11,8 @@ iommufd-y := \ viommu.o iommufd-$(CONFIG_IOMMUFD_NOIOMMU) += hwpt_noiommu.o +iommufd-$(CONFIG_TSM) += tsm.o + iommufd-$(CONFIG_IOMMUFD_TEST) += selftest.o obj-$(CONFIG_IOMMUFD) += iommufd.o
drivers/iommu/iommufd/iommufd_private.h +8 -0
@@ -730,6 +730,14 @@ void iommufd_vdevice_destroy(struct iommufd_object *obj);void iommufd_vdevice_abort (struct iommufd_object *obj);int iommufd_hw_queue_alloc_ioctl (struct iommufd_ucmd *ucmd);void iommufd_hw_queue_destroy (struct iommufd_object *obj); +#ifdef CONFIG_TSM +int iommufd_vdevice_tsm_req_ioctl (struct iommufd_ucmd *ucmd); +#else +static inline int iommufd_vdevice_tsm_req_ioctl (struct iommufd_ucmd *ucmd) +{ +return -EOPNOTSUPP ; +} +#endif #ifdef CONFIG_IOMMUFD_TEST int iommufd_test (struct iommufd_ucmd *ucmd);
drivers/iommu/iommufd/main.c +3 -0
@@ -453,6 +453,7 @@ union ucmd_buffer {struct iommu_veventq_alloc veventq;struct iommu_vfio_ioas vfio_ioas;struct iommu_viommu_alloc viommu; +struct iommu_vdevice_tsm_req tsm_req;#ifdef CONFIG_IOMMUFD_TEST struct iommu_test_cmd test;#endif
@@ -516,6 +517,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = { __reserved),IOCTL_OP (IOMMU_VIOMMU_ALLOC ,iommufd_viommu_alloc_ioctl ,struct iommu_viommu_alloc ,out_viommu_id ), +IOCTL_OP (IOMMU_VDEVICE_TSM_REQ ,iommufd_vdevice_tsm_req_ioctl , +struct iommu_vdevice_tsm_req ,out_tsm_code ), #ifdef CONFIG_IOMMUFD_TEST IOCTL_OP(IOMMU_TEST_CMD, iommufd_test, struct iommu_test_cmd, last), #endif
drivers/iommu/iommufd/tsm.c new file +80 -0
80 lines not on this page · raw message
drivers/iommu/iommufd/viommu.c +3 -0
@@ -2,6 +2,9 @@/* Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES */ #include <linux/file.h> +#include <linux/cleanup.h> +#include <linux/tsm.h> +#include "iommufd_private.h" void iommufd_viommu_destroy (struct iommufd_object *obj)
include/linux/iommufd.h +9 -0
@@ -24,6 +24,7 @@ struct iommufd_ctx;struct iommufd_device ;struct iommufd_viommu_ops ;struct page ; +struct tsm_guest_req_info ;enum iommufd_object_type {IOMMUFD_OBJ_NONE ,
@@ -125,6 +126,10 @@ struct iommufd_vdevice {*/ u64 virt_id; +/* Guest TSM requests accepted by this vdevice; set by vdevice_init(). */ +u64 tsm_req_op_mask; +u32 tsm_tvm_arch; +/* Clean up all driver-specific parts of an iommufd_vdevice */ void (*destroy)(struct iommufd_vdevice *vdev); };
@@ -167,6 +172,7 @@ struct iommufd_hw_queue {* include/uapi/linux/iommufd.h) * If driver has a deinit function to revert what vdevice_init op * does, it should set it to the @vdev->destroy function pointer +* @vdevice_tsm_req: Forward a guest TSM request to a driver-owned vDEVICE * @get_hw_queue_size: Get the size of a driver-defined HW queue structure for a * given @viommu corresponding to @queue_type. Driver should * return 0 if HW queue aren't supported accordingly. It is
@@ -193,6 +199,9 @@ struct iommufd_viommu_ops { struct iommu_user_data_array *array);const size_t vdevice_size;int (*vdevice_init)(struct iommufd_vdevice *vdev); +ssize_t (*vdevice_tsm_req)(struct iommufd_vdevice *vdev, +struct tsm_guest_req_info *info, +u64 *tsm_code);size_t (*get_hw_queue_size)(struct iommufd_viommu *viommu,enum iommu_hw_queue_type queue_type);/* AMD's HW will add hw_queue_init simply using @hw_queue->base_addr */
include/linux/tsm.h +23 -0
@@ -6,6 +6,7 @@#include <linux/types.h> #include <linux/uuid.h> #include <linux/device.h> +#include <uapi/linux/iommufd.h> #define TSM_REPORT_INBLOB_MAX 64#define TSM_REPORT_OUTBLOB_MAX SZ_16M
@@ -123,4 +124,26 @@ int tsm_report_unregister(const struct tsm_report_ops *ops);struct tsm_dev *tsm_register (struct device *parent,struct pci_tsm_ops *ops);void tsm_unregister (struct tsm_dev *tsm_dev);struct tsm_dev *find_tsm_dev (int id); + +#ifdef CONFIG_TSM +/** +* struct tsm_guest_req_info - parameters for a guest-initiated TSM request +* @op: operation for the guest-initiated request +* @tvm_arch: guest TVM architecture +* @req: userspace buffer containing the guest request +* @req_len: request size in bytes, at most INT_MAX +* @resp: userspace buffer for the response +* @resp_len: response buffer capacity in bytes, at most INT_MAX +*/ +struct tsm_guest_req_info { +enum iommu_vdevice_tsm_guest_req_op op; +enum iommu_vdevice_tsm_guest_tvm_arch tvm_arch; +const void __user *req; +u32 req_len; +void __user *resp; +u32 resp_len; +}; +#else +struct tsm_guest_req_info ; +#endif #endif /* __TSM_H */
include/uapi/linux/iommufd.h +82 -0
92 lines not on this page · raw message
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 10/16] iommufd: Add the vdevice TSM request ioctl
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 20 lines
A VMM needs to forward guest-originated TSM commands to the TSM implementation managing an assigned device. The IOMMUFD vdevice identifies that device within its vIOMMU and provides the appropriate dispatch point.
Add
IOMMU_VDEVICE_TSM_REQto send an opaque request to a vdevice and optionally return a response. Define common operation and guest architecture identifiers for CCA, SEV and TDX requests.Let vdevice providers declare the TVM architecture and supported operations in a u64 mask. Check these capabilities before dispatch. Define a one-past-last operation value and assert that every operation fits in the mask; reject out-of-range userspace values before indexing it.
The ioctl return value reports errors or the number of unused buffer bytes, while
tsm_codecarries the TSM-specific result. Use userspace pointers for the internal request and response buffers, and u32 lengths matching the UAPI. Reject lengths aboveINT_MAXso successful residues fit in the ioctl's int return value.
Quoted 6 lines
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
Jason Gunthorpe
maintainerRe: [PATCH v7 10/16] iommufd: Add the vdevice TSM request ioctl
In reply to Aneesh Kumar K.V (Arm)Quoted 18 lines
[ ... 300 lines skipped ... ]
include/uapi/linux/iommufd.h +17
-0+/** +* enum iommu_vdevice_tsm_guest_req_op - Guest TSM request operations +* @TSM_REQ_VALIDATE_MMIO: Validate and map a guest MMIO range for the trusted +* device. +* @TSM_REQ_SET_TDI_STATE: Transition the trusted device to the unlocked, +* locked, or running state. +* @TSM_REQ_SEV_ENABLE_DMA: Enable DMA for an SEV device. +* @TSM_REQ_SEV_DISABLE_DMA: Disable DMA for an SEV device. +* @TSM_REQ_READ_OBJECT: Read bytes from a device object, such as a certificate, +* measurement, or interface report. +* @TSM_REQ_REGEN_OBJECT: Regenerate a device object, such as a measurement or +* interface report. +* @TSM_REQ_OBJECT_INFO: Query the size of a device object. +* @TSM_REQ_MAX: One past the last request operation; not a command. +* +* The request payload and response format depend on the TVM architecture. +*/
include/uapi/linux/iommufd.h struct iommu_hw_queue_alloc { · line 1433
Quoted 10 lines
include/uapi/linux/iommufd.h +10
-0+enum iommu_vdevice_tsm_guest_req_op { +TSM_REQ_VALIDATE_MMIO =1 , +TSM_REQ_SET_TDI_STATE , +TSM_REQ_SEV_ENABLE_DMA , +TSM_REQ_SEV_DISABLE_DMA , +TSM_REQ_READ_OBJECT , +TSM_REQ_REGEN_OBJECT , +TSM_REQ_OBJECT_INFO , +TSM_REQ_MAX , +};
This is still no where close to being well defined enough.
Everything here must tie back to defined spec references, listed in the comments.
I cannot guess what data is passed over any of these APIs, how things like "REGEN_OBJECT" select which object to regen, or what the format of those objects even is. The formats of req/resp are never specified.
The comment for this should probably be *enormous*. Every arch should explain what they do on every one of these ops, and it should be identical if it is structured like this.
I have a strong guess almost none of this is actually general, but lets find out.
You can also put enum kdocs directly before each enum which helps alot when they get as big as this stuff has to get.
My guesses with no information
1) VALIDATE_MMIO why would the hypervisor ever need this? Guest validates MMIO through secure communication to the secure world.
2) TDI_STATE I thought at least RMM did this with an RSI?
3) ENABLE/DISABLE DMA, see #1, must not be done by hypervisor See my reply to Alexey:
https://lore.kernel.org/linux-iommu/[email protected]/
4) READ/REGEN/INFO, what are the objects? What format are they in? How do you select them?
Does this overlap with attestation subsystem? Should attestation subsystem be used to access the SPDM related data?
Update the comment to explain all this, don't need to answer here.
We really need to do these defines along with implementations, that probably will settle alot of the questions above.
Quoted 29 lines
include/uapi/linux/iommufd.h +29
-0+ +/** +* struct iommu_vdevice_tsm_req - ioctl(IOMMU_VDEVICE_TSM_REQ) +* @size: sizeof(struct iommu_vdevice_tsm_req) +* @vdevice_id: vDevice ID the guest request is for +* @op: One of enum iommu_vdevice_tsm_guest_req_op, excluding TSM_REQ_MAX +* @tvm_arch: One of enum iommu_vdevice_tsm_guest_tvm_arch +* @req_len: Size in bytes of the input payload at @req_uptr, at most INT_MAX +* @resp_len: Size in bytes of the output buffer at @resp_uptr, at most INT_MAX +* @req_uptr: Userspace pointer to the guest-provided request payload +* @resp_uptr: Userspace pointer to the guest response buffer +* @out_tsm_code: TSM-specific result code returned by the TSM implementation +* +* Forward a TSM request to the TSM bound vDevice. This is intended for +* guest TSM/TDISP message transport where the host kernel only marshals +* bytes between userspace and the TSM implementation. +* +* The request operation is guest initiated. The vDEVICE implementation +* declares the TVM architecture and operations it accepts; iommufd checks +* both before forwarding the request. +* +* The request payload is read from @req_uptr/@req_len. If a response is +* expected, userspace provides @resp_uptr/@resp_len as writable storage for +* response bytes returned by the TSM path. +* +* The ioctl is only suitable for commands and results that the host kernel +* has no use, the host is only facilitating guest to TSM communication. +*/ +struct iommu_vdevice_tsm_req {
include/uapi/linux/iommufd.h struct iommu_hw_queue_alloc { · line 1472
Quoted 10 lines
include/uapi/linux/iommufd.h +10
-0+__u32 size; +__u32 vdevice_id; +__u32 op; +__u32 tvm_arch; +__u32 req_len; +__u32 resp_len; +__aligned_u64 req_uptr; +__aligned_u64 resp_uptr; +__aligned_u64 out_tsm_code; +};
Should the viommu_id be passed in as well, or we know it implicitly from the vdevice_id?
I think this is probably a fine generic API, it should probably not be "TSM request" so that the other viommu people can reuse the transport.
I would probably try to stick to exactly like the other APIs do with a single enum. Two enums for op/arch is saying already that the ops are not well defined.
So maybe strip this patch out to have no tsm stuff, define no ops and just give a command transport into the viommu driver?
Signature · 1 line
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 10/16] iommufd: Add the vdevice TSM request ioctl
Quoted 31 lines · Jason Gunthorpe
Quoted 18 lines
[ ... 300 lines skipped ... ]
include/uapi/linux/iommufd.h +17
-0+/** +* enum iommu_vdevice_tsm_guest_req_op - Guest TSM request operations +* @TSM_REQ_VALIDATE_MMIO: Validate and map a guest MMIO range for the trusted +* device. +* @TSM_REQ_SET_TDI_STATE: Transition the trusted device to the unlocked, +* locked, or running state. +* @TSM_REQ_SEV_ENABLE_DMA: Enable DMA for an SEV device. +* @TSM_REQ_SEV_DISABLE_DMA: Disable DMA for an SEV device. +* @TSM_REQ_READ_OBJECT: Read bytes from a device object, such as a certificate, +* measurement, or interface report. +* @TSM_REQ_REGEN_OBJECT: Regenerate a device object, such as a measurement or +* interface report. +* @TSM_REQ_OBJECT_INFO: Query the size of a device object. +* @TSM_REQ_MAX: One past the last request operation; not a command. +* +* The request payload and response format depend on the TVM architecture. +*/ include/uapi/linux/iommufd.h struct iommu_hw_queue_alloc { · line 1433
Quoted 10 lines
include/uapi/linux/iommufd.h +10
-0+enum iommu_vdevice_tsm_guest_req_op { +TSM_REQ_VALIDATE_MMIO =1 , +TSM_REQ_SET_TDI_STATE , +TSM_REQ_SEV_ENABLE_DMA , +TSM_REQ_SEV_DISABLE_DMA , +TSM_REQ_READ_OBJECT , +TSM_REQ_REGEN_OBJECT , +TSM_REQ_OBJECT_INFO , +TSM_REQ_MAX , +};This is still no where close to being well defined enough.
Quoted 7 lines
Everything here must tie back to defined spec references, listed in the comments.
I cannot guess what data is passed over any of these APIs, how things like "
REGEN_OBJECT" select which object to regen, or what the format of those objects even is. The formats of req/resp are never specified.
The request and response payload formats are specific to each CoCo backend. Since iommufd treats the payloads as opaque and does not validate their contents, what would documenting those formats here achieve?
The limited validation is intended to prevent this from becoming a general-purpose multiplexer ioctl. It restricts the ioctl to a defined set of guest-initiated operations that the host forwards to the firmware. The path is essentially:
guest -> vmexit -> VMM -> iommufd ioctl -> TSM backend -> firmware
Quoted 4 lines
The comment for this should probably be *enormous*. Every arch should explain what they do on every one of these ops, and it should be identical if it is structured like this.
That would require documenting a significant amount of backend-specific detail in iommufd. Could we instead require each TSM backend's guest_request callback to document its request and response formats?
Quoted 10 lines
I have a strong guess almost none of this is actually general, but lets find out.
You can also put enum kdocs directly before each enum which helps alot when they get as big as this stuff has to get.
My guesses with no information
1)VALIDATE_MMIOwhy would the hypervisor ever need this? Guest validates MMIO through secure communication to the secure world.
With VFIO PCI BARs backed by guest_memfd, the VMM can validate that the IPA and PA values provided by the guest belong to the VFIO device. We previously also used this operation to ask the CoCo TSM backend to map the device memory (the BAR regions). I have removed this operation from the latest series because that responsibility is now split between the VMM and guest_memfd. TDX and SNP still need to be updated accordingly.
Quoted 2 lines
2)
TDI_STATEI thought at least RMM did this with an RSI?
CCA uses RHI. The RHI reaches the VMM, which issues the ioctl to complete the request.
Quoted 7 lines
3) ENABLE/DISABLE DMA, see #1, must not be done by hypervisor See my reply to Alexey:
https://lore.kernel.org/linux-iommu/[email protected]/4) READ/REGEN/INFO, what are the objects? What format are they in? How do you select them?
These objects are cached by the hypervisor. For CCA, they include the certificate, measurement, interface report, and VCA. They are read in response to guest requests. The requests reach the VMM through an RHI, and the VMM issues the ioctl to read the requested object. REGEN refreshes the objects/evidence at the guest's request.
Quoted 3 lines
Does this overlap with attestation subsystem? Should attestation subsystem be used to access the SPDM related data?
I had hoped we could use it, but I could not find an easy way to integrate the two. We need a simple way to read these evidence objects into userspace. I am not yet sure whether evidence-get can be used for that, since it does more than simply read the objects; it also updates evidence.generation, among other things.
Quoted 40 lines
Update the comment to explain all this, don't need to answer here.
We really need to do these defines along with implementations, that probably will settle alot of the questions above.
Quoted 34 lines
include/uapi/linux/iommufd.h +34
-0+ +/** +* struct iommu_vdevice_tsm_req - ioctl(IOMMU_VDEVICE_TSM_REQ) +* @size: sizeof(struct iommu_vdevice_tsm_req) +* @vdevice_id: vDevice ID the guest request is for +* @op: One of enum iommu_vdevice_tsm_guest_req_op, excluding TSM_REQ_MAX +* @tvm_arch: One of enum iommu_vdevice_tsm_guest_tvm_arch +* @req_len: Size in bytes of the input payload at @req_uptr, at most INT_MAX +* @resp_len: Size in bytes of the output buffer at @resp_uptr, at most INT_MAX +* @req_uptr: Userspace pointer to the guest-provided request payload +* @resp_uptr: Userspace pointer to the guest response buffer +* @out_tsm_code: TSM-specific result code returned by the TSM implementation +* +* Forward a TSM request to the TSM bound vDevice. This is intended for +* guest TSM/TDISP message transport where the host kernel only marshals +* bytes between userspace and the TSM implementation. +* +* The request operation is guest initiated. The vDEVICE implementation +* declares the TVM architecture and operations it accepts; iommufd checks +* both before forwarding the request. +* +* The request payload is read from @req_uptr/@req_len. If a response is +* expected, userspace provides @resp_uptr/@resp_len as writable storage for +* response bytes returned by the TSM path. +* +* The ioctl is only suitable for commands and results that the host kernel +* has no use, the host is only facilitating guest to TSM communication. +*/ +struct iommu_vdevice_tsm_req { +__u32 size; +__u32 vdevice_id; +__u32 op; +__u32 tvm_arch; +__u32 req_len;
Quoted 9 lines
include/uapi/linux/iommufd.h struct iommu_hw_queue_alloc { · line 1472
Quoted 5 lines
include/uapi/linux/iommufd.h +5
-0+__u32 resp_len; +__aligned_u64 req_uptr; +__aligned_u64 resp_uptr; +__aligned_u64 out_tsm_code; +};Should the
viommu_idbe passed in as well, or we know it implicitly from thevdevice_id?
We get final callback via
vdev->viommu->ops->vdevice_tsm_req
Quoted 11 lines
I think this is probably a fine generic API, it should probably not be "TSM request" so that the other viommu people can reuse the transport.
I would probably try to stick to exactly like the other APIs do with a single enum. Two enums for op/arch is saying already that the ops are not well defined.
So maybe strip this patch out to have no tsm stuff, define no ops and just give a command transport into the viommu driver?
Are you suggesting one ioctl per each guest request ops?
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 10/16] iommufd: Add the vdevice TSM request ioctl
Quoted 41 lines · Aneesh Kumar K.V
Quoted 31 lines · Jason Gunthorpe
Quoted 18 lines
[ ... 300 lines skipped ... ]
include/uapi/linux/iommufd.h +17
-0+/** +* enum iommu_vdevice_tsm_guest_req_op - Guest TSM request operations +* @TSM_REQ_VALIDATE_MMIO: Validate and map a guest MMIO range for the trusted +* device. +* @TSM_REQ_SET_TDI_STATE: Transition the trusted device to the unlocked, +* locked, or running state. +* @TSM_REQ_SEV_ENABLE_DMA: Enable DMA for an SEV device. +* @TSM_REQ_SEV_DISABLE_DMA: Disable DMA for an SEV device. +* @TSM_REQ_READ_OBJECT: Read bytes from a device object, such as a certificate, +* measurement, or interface report. +* @TSM_REQ_REGEN_OBJECT: Regenerate a device object, such as a measurement or +* interface report. +* @TSM_REQ_OBJECT_INFO: Query the size of a device object. +* @TSM_REQ_MAX: One past the last request operation; not a command. +* +* The request payload and response format depend on the TVM architecture. +*/ include/uapi/linux/iommufd.h struct iommu_hw_queue_alloc { · line 1433
Quoted 10 lines
include/uapi/linux/iommufd.h +10
-0+enum iommu_vdevice_tsm_guest_req_op { +TSM_REQ_VALIDATE_MMIO =1 , +TSM_REQ_SET_TDI_STATE , +TSM_REQ_SEV_ENABLE_DMA , +TSM_REQ_SEV_DISABLE_DMA , +TSM_REQ_READ_OBJECT , +TSM_REQ_REGEN_OBJECT , +TSM_REQ_OBJECT_INFO , +TSM_REQ_MAX , +};This is still no where close to being well defined enough.
Quoted 7 lines
Everything here must tie back to defined spec references, listed in the comments.
I cannot guess what data is passed over any of these APIs, how things like "
REGEN_OBJECT" select which object to regen, or what the format of those objects even is. The formats of req/resp are never specified.
Quoted 4 lines
The request and response payload formats are specific to each CoCo backend. Since iommufd treats the payloads as opaque and does not validate their contents, what would documenting those formats here achieve?
Well if thats the case then no, this is entirely inappropriate.
iommufd requires every single struct to be documented and described. We do not do undocumented opaque structs into drivers.
So figure out exactly what needs to flow here, give it driver specific names for driver specific content and document everything.
This should be done patch by patch as the drivers implement their stuff.
If the data passed is different then it gets a different enum value. No sharing enum values for different structs because the drivers are different.
You can say things like 'the content of the RHI message defined in XYZ' as operations.
Quoted 2 lines
The limited validation is intended to prevent this from becoming a general-purpose multiplexer ioctl.
But we need that for other viommu use cases..
Quoted 5 lines
It restricts the ioctl to a defined set of guest-initiated operations that the host forwards to the firmware. The path is essentially:
guest -> vmexit -> VMM -> iommufd ioctl -> TSM backend -> firmware
Yeah, I'm not adverse to this, so long as it is fully documented and understandable how each system call maps to both the guest and firmware interfaces it is supporting.
Quoted 8 lines
Quoted 4 lines
The comment for this should probably be *enormous*. Every arch should explain what they do on every one of these ops, and it should be identical if it is structured like this.
That would require documenting a significant amount of backend-specific detail in iommufd. Could we instead require each TSM backend's
guest_requestcallback to document its request and response formats?
It needs to be in this header, that is the standard.
Quoted 7 lines
Quoted 4 lines
My guesses with no information
1)VALIDATE_MMIOwhy would the hypervisor ever need this? Guest validates MMIO through secure communication to the secure world.With VFIO PCI BARs backed by
guest_memfd, the VMM can validate that the IPA and PA values provided by the guest belong to the VFIO device.
Why? Who cares? It isn't a trusted validation, and there is a RSI that duplicates this?
Quoted 5 lines
We previously also used this operation to ask the CoCo TSM backend to map the device memory (the BAR regions). I have removed this operation from the latest series because that responsibility is now split between the VMM and
guest_memfd. TDX and SNP still need to be updated accordingly.
Right, it should happen automatically as part of lock.
Quoted 4 lines
Quoted 1 line
2)
TDI_STATEI thought at least RMM did this with an RSI?CCA uses RHI. The RHI reaches the VMM, which issues the ioctl to complete the request.
And then the seq numbers check that this went through OK?
Quoted 18 lines
Quoted 6 lines
3) ENABLE/DISABLE DMA, see #1, must not be done by hypervisor See my reply to Alexey:
https://lore.kernel.org/linux-iommu/[email protected]/4) READ/REGEN/INFO, what are the objects? What format are they in? How do you select them?
These objects are cached by the hypervisor. For CCA, they include the certificate, measurement, interface report, and VCA. They are read in response to guest requests. The requests reach the VMM through an RHI, and the VMM issues the ioctl to read the requested object. REGEN refreshes the objects/evidence at the guest's request.
Quoted 3 lines
Does this overlap with attestation subsystem? Should attestation subsystem be used to access the SPDM related data?
Quoted 5 lines
I had hoped we could use it, but I could not find an easy way to integrate the two. We need a simple way to read these evidence objects into userspace. I am not yet sure whether evidence-get can be used for that, since it does more than simply read the objects; it also updates evidence.generation, among other things.
It should not do that, the generation has to follow the locked state, not the evidence collection.. See my remark to Dave
I think the bigger issue was it had to use the VDEV flavour not the PDEV flavor of RMM commands, and it isn't obvious how we switch from PDEV to VDEV mode on the hypervisor..
Quoted 7 lines
Quoted 7 lines
I think this is probably a fine generic API, it should probably not be "TSM request" so that the other viommu people can reuse the transport.
I would probably try to stick to exactly like the other APIs do with a single enum. Two enums for op/arch is saying already that the ops are not well defined.
Quoted 5 lines
Quoted 3 lines
So maybe strip this patch out to have no tsm stuff, define no ops and just give a command transport into the viommu driver?
Are you suggesting one ioctl per each guest request ops?
One 'u32 op'
Jason
Tian, Kevin
maintainerRE: [PATCH v7 10/16] iommufd: Add the vdevice TSM request ioctl
Quoted 59 lines
From: Jason Gunthorpe <[email protected]>
Sent: Thursday, October 29, 2026 9:01 PMQuoted 5 lines · Aneesh Kumar K.V
Quoted 3 lines · Jason Gunthorpe
Quoted 3 lines
[ ... 300 lines skipped ... ]
+/**
+ * enumiommu_vdevice_tsm_guest_req_op- Guest TSM requestoperations
Quoted 1 line
Quoted 1 line
Quoted 1 line
+ * @TSM_REQ_VALIDATE_MMIO: Validate and map a guest MMIO range
for the trusted
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+ * device.
+ * @TSM_REQ_SET_TDI_STATE: Transition the trusted device to theunlocked,
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
+ * locked, or running state.
+ * @TSM_REQ_SEV_ENABLE_DMA: Enable DMA for an SEV device.
+ * @TSM_REQ_SEV_DISABLE_DMA: Disable DMA for an SEV device.
+ * @TSM_REQ_READ_OBJECT: Read bytes from a device object, such as acertificate,
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+ * measurement, or interface report.
+ * @TSM_REQ_REGEN_OBJECT: Regenerate a device object, such as ameasurement or
Quoted 3 lines
Quoted 3 lines
Quoted 3 lines
+ * interface report.
+ * @TSM_REQ_OBJECT_INFO: Query the size of a device object.
+ * @TSM_REQ_MAX: One past the last request operation; not acommand.
Quoted 2 lines
Quoted 2 lines
Quoted 2 lines
+ *
+ * The request payload and response format depend on the TVMarchitecture.
Quoted 22 lines
Quoted 14 lines
include/uapi/linux/iommufd.h struct iommu_hw_queue_alloc { · line 1433
Quoted 11 lines
+ */
include/uapi/linux/iommufd.h +10
-0+enum iommu_vdevice_tsm_guest_req_op { +TSM_REQ_VALIDATE_MMIO =1 , +TSM_REQ_SET_TDI_STATE , +TSM_REQ_SEV_ENABLE_DMA , +TSM_REQ_SEV_DISABLE_DMA , +TSM_REQ_READ_OBJECT , +TSM_REQ_REGEN_OBJECT , +TSM_REQ_OBJECT_INFO , +TSM_REQ_MAX , +};This is still no where close to being well defined enough.
Quoted 7 lines
Everything here must tie back to defined spec references, listed in the comments.
I cannot guess what data is passed over any of these APIs, how things like "
REGEN_OBJECT" select which object to regen, or what the format of those objects even is. The formats of req/resp are never specified.Quoted 4 lines
The request and response payload formats are specific to each CoCo backend. Since iommufd treats the payloads as opaque and does not validate their contents, what would documenting those formats here achieve?
Well if thats the case then no, this is entirely inappropriate.
Quoted 8 lines
iommufd requires every single struct to be documented and described. We do not do undocumented opaque structs into drivers.
So figure out exactly what needs to flow here, give it driver specific names for driver specific content and document everything.
This should be done patch by patch as the drivers implement their stuff.
some could still be generalized, e.g. setting the TDI state. There doesn't sound a good reason to have vendor specific format for that.
Then if your suggestion of using the attestation subsystem here can work, those OBJECT related operations are generalized too.
In the end the set of vendor specific definition here might be small.
Quoted 10 lines
Quoted 7 lines
Quoted 4 lines
My guesses with no information
1)VALIDATE_MMIOwhy would the hypervisor ever need this? Guest validates MMIO through secure communication to the secure world.With VFIO PCI BARs backed by
guest_memfd, the VMM can validate that the IPA and PA values provided by the guest belong to the VFIO device.Why? Who cares? It isn't a trusted validation, and there is a RSI that duplicates this?
TDX doesn't require this. It's a guest call directly to the secure world.
Quoted 13 lines
Quoted 6 lines
We previously also used this operation to ask the CoCo TSM backend to map the device memory (the BAR regions). I have removed this operation from the latest series because that responsibility is now split between the VMM and
guest_memfd. TDX and SNP still need to be updated accordingly.Right, it should happen automatically as part of lock.
Quoted 4 lines
Quoted 1 line
2)
TDI_STATEI thought at least RMM did this with an RSI?CCA uses RHI. The RHI reaches the VMM, which issues the ioctl to complete the request.
same for TDX, using GHCI to reach the VMM.
Quoted 2 lines
And then the seq numbers check that this went through OK?
which seq numbers in your mind?
Quoted 5 lines
Quoted 4 lines
Quoted 4 lines
3) ENABLE/DISABLE DMA, see #1, must not be done by hypervisor See my reply to Alexey:
https://lore.kernel.org/linux-iommu/[email protected]/
Not required by TDX.
Quoted 21 lines
Quoted 15 lines
Quoted 3 lines
4) READ/REGEN/INFO, what are the objects? What format are they in? How do you select them?
These objects are cached by the hypervisor. For CCA, they include the certificate, measurement, interface report, and VCA. They are read in response to guest requests. The requests reach the VMM through an RHI, and the VMM issues the ioctl to read the requested object. REGEN refreshes the objects/evidence at the guest's request.
Quoted 3 lines
Does this overlap with attestation subsystem? Should attestation subsystem be used to access the SPDM related data?
Quoted 5 lines
I had hoped we could use it, but I could not find an easy way to integrate the two. We need a simple way to read these evidence objects into userspace. I am not yet sure whether evidence-get can be used for that, since it does more than simply read the objects; it also updates evidence.generation, among other things.
Quoted 6 lines
It should not do that, the generation has to follow the locked state, not the evidence collection.. See my remark to Dave
I think the bigger issue was it had to use the VDEV flavour not the PDEV flavor of RMM commands, and it isn't obvious how we switch from PDEV to VDEV mode on the hypervisor..
Can you elaborate this part in a general way? Want to better understand it in the TDX context...
Jason Gunthorpe
maintainerRe: [PATCH v7 10/16] iommufd: Add the vdevice TSM request ioctl
Quoted 4 lines · Kevin
Then if your suggestion of using the attestation subsystem here can work, those OBJECT related operations are generalized too.
In the end the set of vendor specific definition here might be small.
Yeah having now looked more closely at what CCA requires this is my feeling. It looks like it needs nothing beyond what an attestation fd would provide. Possibly TDI state if we don't roll that into the attestation uapi.
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
My guesses with no information
1)VALIDATE_MMIOwhy would the hypervisor ever need this? Guest validates MMIO through secure communication to the secure world.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
With VFIO PCI BARs backed by
guest_memfd, the VMM can validate that the IPA and PA values provided by the guest belong to the VFIO device.Why? Who cares? It isn't a trusted validation, and there is a RSI that duplicates this?
TDX doesn't require this. It's a guest call directly to the secure world.
Yeah, if we need to know the actual current memory shared/private layout after a TDISP state change that should be a first class API, possibly in VFIO.
VFIO already has this information because it needs it to do the guestmemfd/etc invalidations that Intel requires.
Quoted 3 lines
Quoted 1 line
And then the seq numbers check that this went through OK?
which seq numbers in your mind?
CCA uses seq numbers to help secure the RHI. It looks broadly like the guest counts the number of times it does operations and then the RHI relays those operations through the hypervisor. Guest detects tampering by checking the sequences are right through its own secure channel, along with hashes of things..
Like it counts the number of lock/unlock commands. VMM issues a rouge unlock guest can detect it.
Quoted 6 lines
Quoted 3 lines
I think the bigger issue was it had to use the VDEV flavour not the PDEV flavor of RMM commands, and it isn't obvious how we switch from PDEV to VDEV mode on the hypervisor..
Can you elaborate this part in a general way? Want to better understand it in the TDX context...
At least in CCA there are two sets of APIs into RMM for doing SPDM stuff.
One is for the physical device and one is for the virtual device/TDI.
You have to use the right one for the right use case, the VMM has to relay into to the virtual device API, while a non-VM bare metal host has to use the physical to do attestation.
The full best attestation flow for a system would be something like:
1) Bare metal OS does DRTM and gets into Linux
2) Linux leaves drivers unbound and DMA disabled into the initrd/rootfs
3) Every PCI device has a TSM connected. SPDM established. Link IDE setup. PCI device SPDM evidence/measurements collected and logged.
4) VFIO driver is attached
** Now we can attest the hypervisor is secure, untampered, and has
the correct inventory of PCIe devices.
5) VMM starts a TVM
6) TVM does DRTM and gets into Linux
7) Repeat #2
8) Repeat #3, except we are doing lock/run instead of Link IDE. Measurements are still logged (eg to a RTMR)
9) Real guest driver is bound and starts running
** Now we can attest the guest is secure, untamperd, and has the
correct inventory of PCIe devices
So on the hypervisor side we have two logical attestation FDs, one is manipulating the bare metal PCI device intending to bind a bare metal driver.
The other is the relay for the VMM to implement the guest API to control SPDM, intending to operate the PCIe device as a TDI in TDISP mode.
My suggestion recently was to reduce all of this to a single operation 'get attestation fd' inside the iommufd vdevice which returns an FD configured to do the TDI phase for the VMM to use. It presents the same uniform SPDM API we have everywhere. The VMM would have to form that into the VMM Side of whatever arch specific protocol is carrying it to the guest to proxy it into the guest's Attestation subsystem.
Some of if that will work depends on how the security protocol works, like the seq numbers for CCA. That has to map perfectly onto the attestation uAPI so that the VMM is causing only the actions to happen that it is required to do..
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 11/16 PCI/TSM: Remove the legacy guest request interface
In reply to Aneesh Kumar K.V (Arm)Guest TSM requests can now be dispatched through the IOMMUFD vdevice operation. Remove the PCI-device-based guest request entry point, its scope enum and the corresponding PCI/TSM driver callback.
--- drivers/pci/tsm.c | 60 ---------------------------------------- include/linux/pci-tsm.h | 61 +---------------------------------------- 2 files changed, 1 insertion(+), 120 deletions(-)
drivers/pci/tsm.c +0 -60
@@ -375,66 +375,6 @@ int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id) }EXPORT_SYMBOL_GPL (pci_tsm_bind); -/** -* pci_tsm_guest_req() - helper to marshal guest requests to the TSM driver -* @pdev: @pdev representing a bound tdi -* @scope: caller asserts this passthrough request is limited to TDISP operations -* @req_in: Input payload forwarded from the guest -* @in_len: Length of @req_in -* @req_out: Output payload buffer response to the guest -* @out_len: Length of @req_out on input, bytes filled in @req_out on output -* @tsm_code: Optional TSM arch specific result code for the guest TSM -* -* This is a common entry point for requests triggered by userspace KVM-exit -* service handlers responding to TDI information or state change requests. The -* scope parameter limits requests to TDISP state management, or limited debug. -* This path is only suitable for commands and results that are the host kernel -* has no use, the host is only facilitating guest to TSM communication. -* -* Returns 0 on success and -error on failure and positive "residue" on success -* but @req_out is filled with less then @out_len, or @req_out is NULL and a -* residue number of bytes were not consumed from @req_in. On success or -* failure @tsm_code may be populated with a TSM implementation specific result -* code for the guest to consume. -* -* Context: Caller is responsible for calling this within the pci_tsm_bind() -* state of the TDI. -*/ -ssize_t pci_tsm_guest_req (struct pci_dev *pdev,enum pci_tsm_req_scope scope, -sockptr_t req_in,size_t in_len,sockptr_t req_out, -size_t out_len,u64 *tsm_code) -{ -struct pci_tsm_pf0 *tsm_pf0; -struct pci_tdi *tdi; -int rc; - -/* Forbid requests that are not directly related to TDISP operations */ -if (scope >PCI_TSM_REQ_STATE_CHANGE ) -return -EINVAL ; - -ACQUIRE (rwsem_read_intr, lock)(&pci_tsm_rwsem); -if ((rc =ACQUIRE_ERR (rwsem_read_intr, &lock))) -return rc; - -if (!pdev->tsm) -return -ENXIO ; - -if (!is_link_tsm (pdev->tsm->tsm_dev)) -return -ENXIO ; - - tsm_pf0 =to_pci_tsm_pf0 (pdev->tsm); -ACQUIRE (mutex_intr, ops_lock)(&tsm_pf0->lock); -if ((rc =ACQUIRE_ERR (mutex_intr, &ops_lock))) -return rc; - - tdi = pdev->tsm->tdi; -if (!tdi) -return -ENXIO ; -return to_pci_tsm_ops (pdev->tsm)->guest_req (tdi, scope, req_in, in_len, - req_out, out_len, tsm_code); -} -EXPORT_SYMBOL_GPL (pci_tsm_guest_req); -static void pci_tsm_unbind_all (struct pci_dev *pdev) {pci_tsm_walk_fns_reverse (pdev, __pci_tsm_unbind,NULL );
include/linux/pci-tsm.h +1 -60
106 lines not on this page · raw message
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 11/16] PCI/TSM: Remove the legacy guest request interface
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines
Guest TSM requests can now be dispatched through the IOMMUFD vdevice operation. Remove the PCI-device-based guest request entry point, its scope enum and the corresponding PCI/TSM driver callback.
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11
Re: [PATCH v7 11/16] PCI/TSM: Remove the legacy guest request interface
In reply to Aneesh Kumar K.V (Arm)Quoted 3 lines · Aneesh Kumar K.V (Arm)
Guest TSM requests can now be dispatched through the IOMMUFD vdevice operation. Remove the PCI-device-based guest request entry point, its scope enum and the corresponding PCI/TSM driver callback.
Signature · 1 line
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 12/16 PCI/TSM: Add vIOMMU-bound contexts for vdevices
In reply to Aneesh Kumar K.V (Arm)An IOMMUFD vdevice must use the same TEE Security Manager as its vIOMMU. Pinning the vIOMMU backend keeps its tsm_dev registered, but does not preserve the connection between the PF0 PCI device and that TSM.
Add a per-vdevice PCI TSM context. Verify that the PCI device and the vIOMMU use the same tsm_dev, and account active contexts on PF0. Reject disconnect with -EBUSY until all dependent vdevices release their contexts.
With vdevice binding handled by the backend, PCI/TSM no longer has generic bind state to report. Remove the legacy bind and unbind interface. The sysfs "bound" attribute reported the state maintained by that interface. Remove the attribute and its ABI documentation as well.
--- Documentation/ABI/testing/sysfs-bus-pci | 17 +- drivers/pci/tsm.c | 230 ++++++++++-------------- include/linux/pci-tsm.h | 71 +++++--- 3 files changed, 137 insertions(+), 181 deletions(-)
Documentation/ABI/testing/sysfs-bus-pci +3 -14
@@ -654,6 +654,9 @@ Contact: [email protected] Description: (WO) Write the name of the TSM device that was specified to 'connect' to teardown the connection. + The write fails with EBUSY while any vdevice depends on the + connection. Userspace must destroy those vdevices before + disconnecting the link. What: /sys/bus/pci/devices/.../tsm/dsm Contact: [email protected]
@@ -671,20 +674,6 @@ Description: (RO) Return PCI device name of this device's DSM (Device PCIe switch port. This is a "link" TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm. -What: /sys/bus/pci/devices/.../tsm/bound -Contact: [email protected] -Description: (RO) Return the device name of the TSM when the device is in a - TDISP (TEE Device Interface Security Protocol) operational state - (LOCKED, RUN, or ERROR, not UNLOCKED). Bound devices consume - platform TSM resources and depend on the device's configuration - (e.g. BME (Bus Master Enable) and MSE (Memory Space Enable) - among other settings) to remain stable for the duration of the - bound state. This attribute is only visible for devices that - support TDISP operation, and it is only populated after - successful connect and TSM bind. The TSM bind operation is - initiated by VFIO/IOMMUFD. This is a "link" TSM attribute, see - Documentation/ABI/testing/sysfs-class-tsm. - What: /sys/bus/pci/devices/.../authenticated Contact: [email protected] Description:
drivers/pci/tsm.c +92 -138
317 lines not on this page · raw message
include/linux/pci-tsm.h +42 -29
140 lines not on this page · raw message
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 12/16] PCI/TSM: Add vIOMMU-bound contexts for vdevices
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines
An IOMMUFD vdevice must use the same TEE Security Manager as its vIOMMU. Pinning the vIOMMU backend keeps its
tsm_devregistered, but does not preserve the connection between the PF0 PCI device and that TSM.Add a per-vdevice PCI TSM context. Verify that the PCI device and the vIOMMU use the same
tsm_dev, and account active contexts on PF0. Reject disconnect with-EBUSYuntil all dependent vdevices release their contexts.
Quoted 8 lines
With vdevice binding handled by the backend, PCI/TSM no longer has generic bind state to report. Remove the legacy bind and unbind interface. The sysfs "bound" attribute reported the state maintained by that interface. Remove the attribute and its ABI documentation as well.
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=12
Jason Gunthorpe
maintainerRe: [PATCH v7 12/16] PCI/TSM: Add vIOMMU-bound contexts for vdevices
In reply to Aneesh Kumar K.V (Arm)Quoted 28 lines
An IOMMUFD vdevice must use the same TEE Security Manager as its vIOMMU. Pinning the vIOMMU backend keeps its
tsm_devregistered, but does not preserve the connection between the PF0 PCI device and that TSM.Add a per-vdevice PCI TSM context. Verify that the PCI device and the vIOMMU use the same
tsm_dev, and account active contexts on PF0. Reject disconnect with-EBUSYuntil all dependent vdevices release their contexts.With vdevice binding handled by the backend, PCI/TSM no longer has generic bind state to report. Remove the legacy bind and unbind interface. The sysfs "bound" attribute reported the state maintained by that interface. Remove the attribute and its ABI documentation as well.
--- Documentation/ABI/testing/sysfs-bus-pci | 17 +- drivers/pci/tsm.c | 230 ++++++++++-------------- include/linux/pci-tsm.h | 71 +++++--- 3 files changed, 137 insertions(+), 181 deletions(-)Documentation/ABI/testing/sysfs-bus-pci +0
-0
Documentation/ABI/testing/sysfs-bus-pci Contact: [email protected] · line 659
Quoted 6 lines
Documentation/ABI/testing/sysfs-bus-pci +3
-0@@ -654,6 +654,9 @@ Contact: [email protected] Description: (WO) Write the name of the TSM device that was specified to 'connect' to teardown the connection. + The write fails with EBUSY while any vdevice depends on the + connection. Userspace must destroy those vdevices before + disconnecting the link.
Probably just generalize this 'EBUSY if the TSM is in use'
Signature · 1 line
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 13/16 iommufd/viommu: Select vIOMMU operations before allocation
In reply to Aneesh Kumar K.V (Arm)Physical IOMMU drivers currently provide the vIOMMU size and initialization callbacks through iommu_ops. The initialization callback then selects and installs the corresponding iommufd_viommu_ops.
Add iommu_ops::get_viommu_ops() to select the operations from the physical device and requested vIOMMU type before allocation. Move the size and initialization callbacks into iommufd_viommu_ops. The core can then validate the selected operations, allocate the driver structure, initialize it, and install the operations.
Convert AMD, Arm SMMUv3, Tegra CMDQV, and the selftest backend to the new interface.
Selecting the operations early allows subsequent changes to use implementation-specific vIOMMU properties, including when validating the parent HWPT.
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Based on original patch by Jason Gunthorpe <[email protected]>
--- drivers/iommu/amd/iommu.c | 3 +- drivers/iommu/amd/iommufd.c | 18 ++++++--- drivers/iommu/amd/iommufd.h | 11 ++++-- drivers/iommu/amd/nested.c | 4 +- .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 37 +++++++++++-------- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 7 ++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 16 ++++---- .../iommu/arm/arm-smmu-v3/tegra241-cmdqv.c | 24 ++++++++---- drivers/iommu/iommufd/selftest.c | 31 +++++++++++----- drivers/iommu/iommufd/viommu.c | 32 ++++++++-------- include/linux/iommu.h | 20 +++------- include/linux/iommufd.h | 14 +++++++ 12 files changed, 129 insertions(+), 88 deletions(-)
drivers/iommu/amd/iommu.c +1 -2
@@ -3211,8 +3211,7 @@ const struct iommu_ops amd_iommu_ops = { .is_attach_deferred = amd_iommu_is_attach_deferred, .def_domain_type = amd_iommu_def_domain_type, .page_response = amd_iommu_page_response, - .get_viommu_size = amd_iommufd_get_viommu_size, - .viommu_init = amd_iommufd_viommu_init, + .get_viommu_ops = amd_iommufd_get_viommu_ops, };#ifdef CONFIG_IRQ_REMAP
drivers/iommu/amd/iommufd.c +13 -5
@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_tyreturn hwinfo; } -size_t amd_iommufd_get_viommu_size (struct device *dev,enum iommu_viommu_type viommu_type) +static size_t amd_iommufd_get_viommu_size (struct device *dev, +enum iommu_viommu_type viommu_type) { return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core); } -int amd_iommufd_viommu_init (struct iommufd_viommu *viommu,struct iommu_domain *parent, -const struct iommu_user_data *user_data) +const struct iommufd_viommu_ops * +amd_iommufd_get_viommu_ops (struct device *dev,enum iommu_viommu_type viommu_type) +{ +return &amd_viommu_ops; +} + +int amd_iommufd_viommu_init (struct iommufd_viommu *viommu, +struct device *dev,struct iommu_domain *parent, +const struct iommu_user_data *user_data) {unsigned long flags;struct protection_domain *pdom =to_pdomain (parent);
@@ -47,8 +55,6 @@ int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *xa_init_flags (&aviommu->gdomid_array,XA_FLAGS_ALLOC1 ); aviommu->parent = pdom; - viommu->ops = &amd_viommu_ops; -spin_lock_irqsave (&pdom->lock, flags);list_add (&aviommu->pdom_list, &pdom->viommu_list);spin_unlock_irqrestore (&pdom->lock, flags);
@@ -73,5 +79,7 @@ static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)* struct iommufd_viommu_ops - vIOMMU specific operations */ static const struct iommufd_viommu_ops amd_viommu_ops = { + .get_viommu_size = amd_iommufd_get_viommu_size, + .viommu_init = amd_iommufd_viommu_init, .destroy = amd_iommufd_viommu_destroy, };
drivers/iommu/amd/iommufd.h +7 -4
@@ -8,13 +8,16 @@#if IS_ENABLED (CONFIG_AMD_IOMMU_IOMMUFD )void *amd_iommufd_hw_info (struct device *dev,u32 *length,enum iommu_hw_info_type *type); -size_t amd_iommufd_get_viommu_size (struct device *dev,enum iommu_viommu_type viommu_type); -int amd_iommufd_viommu_init (struct iommufd_viommu *viommu,struct iommu_domain *parent, -const struct iommu_user_data *user_data); +const struct iommufd_viommu_ops * +amd_iommufd_get_viommu_ops (struct device *dev, +enum iommu_viommu_type viommu_type); +int amd_iommufd_viommu_init (struct iommufd_viommu *viommu, +struct device *dev,struct iommu_domain *parent, +const struct iommu_user_data *user_data);#else #define amd_iommufd_hw_info NULL#define amd_iommufd_viommu_init NULL -#define amd_iommufd_get_viommu_size NULL +#define amd_iommufd_get_viommu_ops NULL#endif /* CONFIG_AMD_IOMMU_IOMMUFD */ #endif /* AMD_IOMMUFD_H */
drivers/iommu/amd/nested.c +2 -2
@@ -90,7 +90,7 @@ static void *gdom_info_load_or_alloc_locked(struct xarray *xa,/* * This function is assigned to struct iommufd_viommu_ops.alloc_domain_nested() -* during the call to struct iommu_ops.viommu_init(). +* when the vIOMMU operations are selected. */ struct iommu_domain * amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
@@ -198,7 +198,7 @@ static void set_dte_nested(struct amd_iommu *iommu, struct iommu_domain *dom,/* * The nest parent domain is attached during the call to the -* struct iommu_ops.viommu_init(), which will be stored as part +* struct iommufd_viommu_ops.viommu_init(), which will be stored as part * of the struct amd_iommu_viommu.parent. */ if (WARN_ON (!ndom->viommu || !ndom->viommu->parent))
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +22 -15
@@ -417,20 +417,29 @@ int arm_vsmmu_cache_invalidate(struct iommufd_viommu *viommu,return ret; } +static size_t arm_vsmmu_get_size (struct device *dev, +enum iommu_viommu_type viommu_type) +{ + return VIOMMU_STRUCT_SIZE(struct arm_vsmmu, core); +} +static const struct iommufd_viommu_ops arm_vsmmu_ops = { + .get_viommu_size = arm_vsmmu_get_size, + .viommu_init = arm_vsmmu_init, .alloc_domain_nested = arm_vsmmu_alloc_domain_nested, .cache_invalidate = arm_vsmmu_cache_invalidate, .vdevice_init = arm_vsmmu_vdevice_init, }; -size_t arm_smmu_get_viommu_size (struct device *dev, -enum iommu_viommu_type viommu_type) +const struct iommufd_viommu_ops * +arm_smmu_get_viommu_ops (struct device *dev, +enum iommu_viommu_type viommu_type) {struct arm_smmu_master *master =dev_iommu_priv_get (dev);struct arm_smmu_device *smmu = master->smmu;if (!(smmu->features &ARM_SMMU_FEAT_NESTING )) -return 0 ; +return NULL ;/* * FORCE_SYNC is not set with FEAT_NESTING. Some study of the exact HW
@@ -438,7 +447,7 @@ size_t arm_smmu_get_viommu_size(struct device *dev,* any change to remove this. */ if (WARN_ON (smmu->options &ARM_SMMU_OPT_CMDQ_FORCE_SYNC )) -return 0 ; +return NULL ;/* * Must support some way to prevent the VM from bypassing the cache
@@ -450,19 +459,19 @@ size_t arm_smmu_get_viommu_size(struct device *dev,*/ if (!arm_smmu_master_canwbs (master) && !(smmu->features &ARM_SMMU_FEAT_S2FWB )) -return 0 ; +return NULL ;if (viommu_type ==IOMMU_VIOMMU_TYPE_ARM_SMMUV3 ) - return VIOMMU_STRUCT_SIZE(struct arm_vsmmu, core); +return &arm_vsmmu_ops; -if (!smmu->impl_ops || !smmu->impl_ops->get_viommu_size) -return 0 ; -return smmu->impl_ops->get_viommu_size (viommu_type); +if (!smmu->impl_ops || !smmu->impl_ops->get_viommu_ops) +return NULL ; +return smmu->impl_ops->get_viommu_ops (viommu_type); } -int arm_vsmmu_init (struct iommufd_viommu *viommu, -struct iommu_domain *parent_domain, -const struct iommu_user_data *user_data) +int arm_vsmmu_init (struct iommufd_viommu *viommu,struct device *dev, +struct iommu_domain *parent_domain, +const struct iommu_user_data *user_data) { struct arm_vsmmu *vsmmu = container_of(viommu, struct arm_vsmmu, core); struct arm_smmu_device *smmu =
@@ -477,10 +486,8 @@ int arm_vsmmu_init(struct iommufd_viommu *viommu,/* FIXME Move VMID allocation from the S2 domain allocation to here */ vsmmu->vmid = s2_parent->s2_cfg.vmid; -if (viommu->type ==IOMMU_VIOMMU_TYPE_ARM_SMMUV3 ) { - viommu->ops = &arm_vsmmu_ops; +if (viommu->type ==IOMMU_VIOMMU_TYPE_ARM_SMMUV3 )return 0 ; - }return smmu->impl_ops->vsmmu_init (vsmmu, user_data); }
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3 -4
@@ -4389,8 +4389,7 @@ static const struct iommu_ops arm_smmu_ops = { .get_resv_regions = arm_smmu_get_resv_regions, .page_response = arm_smmu_page_response, .def_domain_type = arm_smmu_def_domain_type, - .get_viommu_size = arm_smmu_get_viommu_size, - .viommu_init = arm_vsmmu_init, + .get_viommu_ops = arm_smmu_get_viommu_ops, .user_pasid_table = 1, .owner = THIS_MODULE, .default_domain_ops = &(const struct iommu_domain_ops ) {
@@ -5489,8 +5488,8 @@ static struct arm_smmu_device *arm_smmu_impl_probe(struct arm_smmu_device *smmu) ops = new_smmu->impl_ops;if (ops) { -/* get_viommu_size and vsmmu_init ops must be paired */ -if (WARN_ON (!ops->get_viommu_size != !ops->vsmmu_init)) { +/* get_viommu_ops and vsmmu_init ops must be paired */ +if (WARN_ON (!ops->get_viommu_ops != !ops->vsmmu_init)) { ret = -EINVAL ; goto err_remove; }
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +9 -7
@@ -886,7 +886,8 @@ struct arm_smmu_impl_ops {*/ void *(*hw_info)(struct arm_smmu_device *smmu,u32 *length,enum iommu_hw_info_type *type); -size_t (*get_viommu_size)(enum iommu_viommu_type viommu_type); +const struct iommufd_viommu_ops *(*get_viommu_ops)( +enum iommu_viommu_type viommu_type);int (*vsmmu_init)(struct arm_vsmmu *vsmmu,const struct iommu_user_data *user_data); };
@@ -1260,11 +1261,12 @@ struct arm_vsmmu {#if IS_ENABLED (CONFIG_ARM_SMMU_V3_IOMMUFD )void *arm_smmu_hw_info (struct device *dev,u32 *length,enum iommu_hw_info_type *type); -size_t arm_smmu_get_viommu_size (struct device *dev, -enum iommu_viommu_type viommu_type); -int arm_vsmmu_init (struct iommufd_viommu *viommu, -struct iommu_domain *parent_domain, -const struct iommu_user_data *user_data); +const struct iommufd_viommu_ops * +arm_smmu_get_viommu_ops (struct device *dev, +enum iommu_viommu_type viommu_type); +int arm_vsmmu_init (struct iommufd_viommu *viommu,struct device *dev, +struct iommu_domain *parent_domain, +const struct iommu_user_data *user_data);int arm_smmu_attach_prepare_vmaster (struct arm_smmu_attach_state *state,struct arm_smmu_nested_domain *nested_domain);void arm_smmu_attach_commit_vmaster (struct arm_smmu_attach_state *state);
@@ -1276,7 +1278,7 @@ arm_vsmmu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,int arm_vsmmu_cache_invalidate (struct iommufd_viommu *viommu,struct iommu_user_data_array *array);#else -#define arm_smmu_get_viommu_size NULL +#define arm_smmu_get_viommu_ops NULL#define arm_smmu_hw_info NULL#define arm_vsmmu_init NULL#define arm_vsmmu_alloc_domain_nested NULL
drivers/iommu/arm/arm-smmu-v3/tegra241-cmdqv.c +17 -7
@@ -892,11 +892,14 @@ static void *tegra241_cmdqv_hw_info(struct arm_smmu_device *smmu, u32 *length,return info; } -static size_t tegra241_cmdqv_get_vintf_size (enum iommu_viommu_type viommu_type) +static const struct iommufd_viommu_ops tegra241_cmdqv_viommu_ops; + +static const struct iommufd_viommu_ops * +tegra241_cmdqv_get_viommu_ops (enum iommu_viommu_type viommu_type) {if (viommu_type !=IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV ) -return 0 ; - return VIOMMU_STRUCT_SIZE(struct tegra241_vintf, vsmmu.core); +return NULL ; +return &tegra241_cmdqv_viommu_ops; }static struct arm_smmu_impl_ops tegra241_cmdqv_impl_ops = {
@@ -907,7 +910,7 @@ static struct arm_smmu_impl_ops tegra241_cmdqv_impl_ops = { .device_remove = tegra241_cmdqv_remove, /* For user-space use */ .hw_info = tegra241_cmdqv_hw_info, - .get_viommu_size = tegra241_cmdqv_get_vintf_size, + .get_viommu_ops = tegra241_cmdqv_get_viommu_ops, .vsmmu_init = tegra241_cmdqv_init_vintf_user, };
@@ -1289,7 +1292,15 @@ static int tegra241_vintf_init_vsid(struct iommufd_vdevice *vdev)return 0 ; } -static struct iommufd_viommu_ops tegra241_cmdqv_viommu_ops = { +static size_t tegra241_cmdqv_get_vintf_size (struct device *dev, +enum iommu_viommu_type viommu_type) +{ + return VIOMMU_STRUCT_SIZE(struct tegra241_vintf, vsmmu.core); +} + +static const struct iommufd_viommu_ops tegra241_cmdqv_viommu_ops = { + .get_viommu_size = tegra241_cmdqv_get_vintf_size, + .viommu_init = arm_vsmmu_init, .destroy = tegra241_cmdqv_destroy_vintf_user, .alloc_domain_nested = arm_vsmmu_alloc_domain_nested,/* Non-accelerated commands will be still handled by the kernel */
@@ -1312,7 +1323,7 @@ tegra241_cmdqv_init_vintf_user(struct arm_vsmmu *vsmmu,int ret;/* -* Unsupported type should be rejected by tegra241_cmdqv_get_vintf_size. +* Unsupported type should be rejected by tegra241_cmdqv_get_viommu_ops. * Seeing one here indicates a kernel bug or some data corruption. */ if (WARN_ON (vsmmu->core.type !=IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV ))
@@ -1364,7 +1375,6 @@ tegra241_cmdqv_init_vintf_user(struct arm_vsmmu *vsmmu,dev_dbg (cmdqv->dev,"VINTF%u: allocated with vmid (%d)\n" , vintf->idx, vintf->vsmmu.vmid); - vsmmu->core.ops = &tegra241_cmdqv_viommu_ops;return 0 ; free_mmap:
drivers/iommu/iommufd/selftest.c +21 -10
@@ -774,7 +774,19 @@ static int mock_hw_queue_init_phys(struct iommufd_hw_queue *hw_queue, u32 index,return rc; } -static struct iommufd_viommu_ops mock_viommu_ops = { +static int mock_viommu_init (struct iommufd_viommu *viommu,struct device *dev, +struct iommu_domain *parent_domain, +const struct iommu_user_data *user_data); + +static size_t mock_get_viommu_size (struct device *dev, +enum iommu_viommu_type viommu_type) +{ + return VIOMMU_STRUCT_SIZE(struct mock_viommu, core); +} + +static const struct iommufd_viommu_ops mock_viommu_ops = { + .get_viommu_size = mock_get_viommu_size, + .viommu_init = mock_viommu_init, .destroy = mock_viommu_destroy, .alloc_domain_nested = mock_viommu_alloc_domain_nested, .cache_invalidate = mock_viommu_cache_invalidate,
@@ -782,17 +794,18 @@ static struct iommufd_viommu_ops mock_viommu_ops = { .hw_queue_init_phys = mock_hw_queue_init_phys, }; -static size_t mock_get_viommu_size (struct device *dev, -enum iommu_viommu_type viommu_type) +static const struct iommufd_viommu_ops * +mock_get_viommu_ops (struct device *dev, +enum iommu_viommu_type viommu_type) {if (viommu_type !=IOMMU_VIOMMU_TYPE_SELFTEST ) -return 0 ; - return VIOMMU_STRUCT_SIZE(struct mock_viommu, core); +return NULL ; +return &mock_viommu_ops; }static int mock_viommu_init (struct iommufd_viommu *viommu, -struct iommu_domain *parent_domain, -const struct iommu_user_data *user_data) +struct device *dev,struct iommu_domain *parent_domain, +const struct iommu_user_data *user_data) {struct mock_iommu_device *mock_iommu =container_of ( viommu->iommu_dev, struct mock_iommu_device, iommu_dev);
@@ -834,7 +847,6 @@ static int mock_viommu_init(struct iommufd_viommu *viommu,mutex_init (&mock_viommu->queue_mutex); mock_viommu->s2_parent =to_mock_domain (parent_domain); - viommu->ops = &mock_viommu_ops;return 0 ; err_destroy_mmap:
@@ -861,8 +873,7 @@ static const struct iommu_ops mock_ops = { .probe_device = mock_probe_device, .page_response = mock_domain_page_response, .user_pasid_table = true, - .get_viommu_size = mock_get_viommu_size, - .viommu_init = mock_viommu_init, + .get_viommu_ops = mock_get_viommu_ops, };static void mock_domain_free_nested (struct iommu_domain *domain)
drivers/iommu/iommufd/viommu.c +15 -17
@@ -32,7 +32,7 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)struct iommufd_viommu *viommu;struct iommufd_device *idev;struct iommu_device *iommu_dev; -const struct iommu_ops *ops; +const struct iommufd_viommu_ops *ops;size_t viommu_size;int rc;
@@ -48,23 +48,25 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) rc = -EOPNOTSUPP ; goto out_put_idev; } - ops = iommu_dev->ops; -if (!ops->get_viommu_size || !ops->viommu_init) { +if (!iommu_dev->ops->get_viommu_ops) { rc = -EOPNOTSUPP ; goto out_put_idev; } - - viommu_size = ops->get_viommu_size (idev->dev, cmd->type); -if (!viommu_size) { + ops = iommu_dev->ops->get_viommu_ops (idev->dev, cmd->type); +if (!ops) { rc = -EOPNOTSUPP ; goto out_put_idev; } -/* -* It is a driver bug for providing a viommu_size smaller than the core -* vIOMMU structure size +* It is a driver bug to omit the required operations or provide a size +* smaller than the core vIOMMU structure. */ -if (WARN_ON_ONCE (viommu_size <sizeof (*viommu))) { +if (WARN_ON_ONCE (!ops->get_viommu_size || !ops->viommu_init)) { + rc = -EOPNOTSUPP ; + goto out_put_idev; + } + viommu_size = ops->get_viommu_size (idev->dev, cmd->type); +if (!viommu_size ||WARN_ON_ONCE (viommu_size <sizeof (*viommu))) { rc = -EOPNOTSUPP ; goto out_put_idev; }
@@ -103,16 +105,12 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)*/ viommu->iommu_dev = iommu_dev; - rc = ops->viommu_init (viommu, hwpt_paging->common.domain, + rc = ops->viommu_init (viommu, idev->dev, + hwpt_paging->common.domain, user_data.len ? &user_data :NULL );if (rc) goto out_put_hwpt; - -/* It is a driver bug that viommu->ops isn't filled */ -if (WARN_ON_ONCE (!viommu->ops)) { - rc = -EOPNOTSUPP ; - goto out_put_hwpt; - } + viommu->ops = ops; cmd->out_viommu_id = viommu->obj.id; rc =iommufd_ucmd_respond (ucmd,sizeof (*cmd));
include/linux/iommu.h +5 -15
@@ -46,6 +46,7 @@ struct iommu_dma_msi_cookie;struct iommu_fault_param ;struct iommufd_ctx ;struct iommufd_viommu ; +struct iommufd_viommu_ops ;struct msi_desc ;struct msi_msg ;
@@ -669,16 +670,8 @@ __iommu_copy_struct_to_user(const struct iommu_user_data *dst_data,* - IOMMU_DOMAIN_DMA: must use a dma domain * - 0: use the default setting * @default_domain_ops: the default ops for domains -* @get_viommu_size: Get the size of a driver-level vIOMMU structure for a given -* @dev corresponding to @viommu_type. Driver should return 0 -* if vIOMMU isn't supported accordingly. It is required for -* driver to use the VIOMMU_STRUCT_SIZE macro to sanitize the -* driver-level vIOMMU structure related to the core one -* @viommu_init: Init the driver-level struct of an iommufd_viommu on a physical -* IOMMU instance @viommu->iommu_dev, as the set of virtualization -* resources shared/passed to user space IOMMU instance. Associate -* it with a nesting @parent_domain. It is required for driver to -* set @viommu->ops pointing to its own viommu_ops +* @get_viommu_ops: Return the vIOMMU operations supported by @dev for a type. +* Return NULL if the type is unsupported. * @owner: Driver module providing these ops * @identity_domain: An always available, always attachable identity * translation.
@@ -729,11 +722,8 @@ struct iommu_ops {int (*def_domain_type)(struct device *dev); -size_t (*get_viommu_size)(struct device *dev, -enum iommu_viommu_type viommu_type); -int (*viommu_init)(struct iommufd_viommu *viommu, -struct iommu_domain *parent_domain, -const struct iommu_user_data *user_data); +const struct iommufd_viommu_ops *(*get_viommu_ops)( +struct device *dev,enum iommu_viommu_type viommu_type);const struct iommu_domain_ops *default_domain_ops;struct module *owner;
include/linux/iommufd.h +14 -0
@@ -150,6 +150,15 @@ struct iommufd_hw_queue {/** * struct iommufd_viommu_ops - vIOMMU specific operations +* @get_viommu_size: Get the size of a driver-level vIOMMU structure for a given +* @dev corresponding to @viommu_type. Driver should return 0 +* if vIOMMU isn't supported accordingly. It is required for +* driver to use the VIOMMU_STRUCT_SIZE macro to sanitize the +* driver-level vIOMMU structure related to the core one +* @viommu_init: Init the driver-level struct of an iommufd_viommu on a physical +* IOMMU instance @viommu->iommu_dev, as the set of virtualization +* resources shared/passed to user space IOMMU instance. Associate +* it with a nesting @parent_domain. * @destroy: Clean up all driver-specific parts of an iommufd_viommu. The memory * of the vIOMMU will be free-ed by iommufd core after calling this op * @alloc_domain_nested: Allocate a IOMMU_DOMAIN_NESTED on a vIOMMU that holds a
@@ -191,6 +200,11 @@ struct iommufd_hw_queue {* does, it should set it to the @hw_queue->destroy pointer */ struct iommufd_viommu_ops { +size_t (*get_viommu_size)(struct device *dev, +enum iommu_viommu_type type); +int (*viommu_init)(struct iommufd_viommu *viommu,struct device *dev, +struct iommu_domain *parent_domain, +const struct iommu_user_data *user_data);void (*destroy)(struct iommufd_viommu *viommu); struct iommu_domain *(*alloc_domain_nested)( struct iommufd_viommu *viommu, u32 flags,
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [Critical] iommu: type confusion in iommufd_viommu_alloc_ioctl() parent domain - [High] iommu: dead code…
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] iommu: type confusion in iommufd_viommu_alloc_ioctl() parent domain
- [High] iommu: dead code because amd_iommu_alloc_domain_nested is not assigned
- [Low] iommu: missing viommu_type validation in amd_iommufd_get_viommu_ops --
commit ea74b8b169fc38edaee15fa883c362684ddec72d
Author: Aneesh Kumar K.V (Arm) <[email protected]>
Subject: iommufd/viommu: Select vIOMMU operations before allocation
This commit introduces a new get_viommu_ops callback to select vIOMMU operations based on the device and requested type before allocation. It moves the size and initialization callbacks into iommufd_viommu_ops and converts multiple physical IOMMU drivers to use the new interface.
Quoted 12 lines
drivers/iommu/amd/iommufd.c +2
-1@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_tyreturn hwinfo; } -size_t amd_iommufd_get_viommu_size (struct device *dev,enum iommu_viommu_type viommu_type) +static size_t amd_iommufd_get_viommu_size (struct device *dev, +enum iommu_viommu_type viommu_type) {
drivers/iommu/amd/iommufd.c void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty · line 45
Quoted 10 lines
drivers/iommu/amd/iommufd.c +5
-2return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core); } -int amd_iommufd_viommu_init (struct iommufd_viommu *viommu,struct iommu_domain *parent, -const struct iommu_user_data *user_data) +const struct iommufd_viommu_ops * +amd_iommufd_get_viommu_ops (struct device *dev,enum iommu_viommu_type viommu_type) +{ +return &amd_viommu_ops; +}
[Severity: Low]
Does this function need to validate the requested viommu_type?
Currently, amd_iommufd_get_viommu_ops() unconditionally returns the ops structure without checking the type argument. If a user requests an arbitrary type like IOMMU_VIOMMU_TYPE_ARM_SMMUV3 on an AMD device, the core iommufd framework might track the vIOMMU under a mismatched architectural type identifier.
The ARM and selftest backends appear to validate this argument against their supported hardware types before returning the operations.
[ ... ]
drivers/iommu/amd/iommufd.c static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu) · line 85
Quoted 8 lines
drivers/iommu/amd/iommufd.c +2
-0@@ -73,5 +79,7 @@ static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)* struct iommufd_viommu_ops - vIOMMU specific operations */ static const struct iommufd_viommu_ops amd_viommu_ops = { + .get_viommu_size = amd_iommufd_get_viommu_size, + .viommu_init = amd_iommufd_viommu_init, .destroy = amd_iommufd_viommu_destroy, };
[Severity: High]
Is the alloc_domain_nested callback missing from this operations structure?
The commit message and comments in amd_iommu_alloc_domain_nested() indicate it should be assigned to the viommu operations. However, it is omitted here.
If a user attempts to allocate an AMD nested domain using IOMMU_HWPT_ALLOC, won't the core framework reject it with -EOPNOTSUPP because viommu->ops->alloc_domain_nested is NULL?
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0
[ ... ]
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) · line 110
Quoted 8 lines
drivers/iommu/iommufd/viommu.c +2
-1@@ -103,16 +105,12 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)*/ viommu->iommu_dev = iommu_dev; - rc = ops->viommu_init (viommu, hwpt_paging->common.domain, + rc = ops->viommu_init (viommu, idev->dev, + hwpt_paging->common.domain, user_data.len ? &user_data :NULL );
[Severity: Critical]
Is there a missing check to ensure the domain is owned by the same driver before passing it to viommu_init?
In iommufd_viommu_alloc_ioctl(), hwpt_paging->common.domain is passed directly to the device driver's viommu_init callback. Unlike in iommufd_hw_pagetable_alloc() which checks if parent->common.domain->owner matches the ops, this check appears to be omitted here.
If a user allocates a paging HWPT for a mock device, and then calls IOMMU_VIOMMU_ALLOC passing an AMD device ID but the mock HWPT ID, could the receiving driver blindly cast the mismatched domain? For instance, AMD's viommu_init uses to_pdomain() on the parent domain. This looks like it could lead to type confusion and out-of-bounds accesses on the domain structure.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=13
Aneesh Kumar K.V
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Quoted 47 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] iommu: type confusion iniommufd_viommu_alloc_ioctl()parent domain
- [High] iommu: dead code becauseamd_iommu_alloc_domain_nestedis not assigned
- [Low] iommu: missingviommu_typevalidation inamd_iommufd_get_viommu_ops--commit
ea74b8b169fc38edaee15fa883c362684ddec72d
Author: Aneesh Kumar K.V (Arm) <[email protected]>
Subject: iommufd/viommu: Select vIOMMU operations before allocationThis commit introduces a new
get_viommu_opscallback to select vIOMMU operations based on the device and requested type before allocation. It moves the size and initialization callbacks intoiommufd_viommu_opsand converts multiple physical IOMMU drivers to use the new interface.Quoted 12 lines
drivers/iommu/amd/iommufd.c +2
-1@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_tyreturn hwinfo; } -size_t amd_iommufd_get_viommu_size (struct device *dev,enum iommu_viommu_type viommu_type) +static size_t amd_iommufd_get_viommu_size (struct device *dev, +enum iommu_viommu_type viommu_type) {drivers/iommu/amd/iommufd.c void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty · line 45
Quoted 10 lines
drivers/iommu/amd/iommufd.c +5
-2return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core); } -int amd_iommufd_viommu_init (struct iommufd_viommu *viommu,struct iommu_domain *parent, -const struct iommu_user_data *user_data) +const struct iommufd_viommu_ops * +amd_iommufd_get_viommu_ops (struct device *dev,enum iommu_viommu_type viommu_type) +{ +return &amd_viommu_ops; +}[Severity: Low]
Does this function need to validate the requestedviommu_type?Currently,
amd_iommufd_get_viommu_ops()unconditionally returns the ops structure without checking the type argument. If a user requests an arbitrary type likeIOMMU_VIOMMU_TYPE_ARM_SMMUV3on an AMD device, the core iommufd framework might track the vIOMMU under a mismatched architectural type identifier.
Quoted 5 lines
The ARM and selftest backends appear to validate this argument against their supported hardware types before returning the operations.
[ ... ]
No vIOMMU type has been added for any IOMMU other than Arm. The documentation also considers IOMMU_VIOMMU_TYPE_DEFAULT reserved.
Quoted 12 lines
drivers/iommu/amd/iommufd.c static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu) · line 85
Quoted 8 lines
drivers/iommu/amd/iommufd.c +2
-0@@ -73,5 +79,7 @@ static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)* struct iommufd_viommu_ops - vIOMMU specific operations */ static const struct iommufd_viommu_ops amd_viommu_ops = { + .get_viommu_size = amd_iommufd_get_viommu_size, + .viommu_init = amd_iommufd_viommu_init, .destroy = amd_iommufd_viommu_destroy, };[Severity: High]
Is thealloc_domain_nestedcallback missing from this operations structure?
Quoted 7 lines
The commit message and comments in
amd_iommu_alloc_domain_nested()indicate it should be assigned to the viommu operations. However, it is omitted here.If a user attempts to allocate an AMD nested domain using
IOMMU_HWPT_ALLOC, won't the core framework reject it with-EOPNOTSUPPbecauseviommu->ops->alloc_domain_nestedis NULL?
It is unclear whether nested domain support is fully integrated for AMD. I see amd_iommu_alloc_domain_nested(), but cannot find any callers.
Quoted 9 lines
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0[ ... ]
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0@@ -103,16 +105,12 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)*/ viommu->iommu_dev = iommu_dev;
Quoted 9 lines
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) · line 110
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +2
-1- rc = ops->viommu_init (viommu, hwpt_paging->common.domain, + rc = ops->viommu_init (viommu, idev->dev, + hwpt_paging->common.domain, user_data.len ? &user_data :NULL );[Severity: Critical]
Is there a missing check to ensure the domain is owned by the same driver before passing it toviommu_init?
The viommu_init callback needs to perform this check. For example, the Arm implementation does:
ret = arm_smmu_validate_parent(smmu, parent_domain); if (ret) return ret;
Quoted 11 lines
In
iommufd_viommu_alloc_ioctl(),hwpt_paging->common.domainis passed directly to the device driver'sviommu_initcallback. Unlike iniommufd_hw_pagetable_alloc()which checks ifparent->common.domain->ownermatches the ops, this check appears to be omitted here.If a user allocates a paging HWPT for a mock device, and then calls
IOMMU_VIOMMU_ALLOCpassing an AMD device ID but the mock HWPT ID, could the receiving driver blindly cast the mismatched domain? For instance, AMD'sviommu_initusesto_pdomain()on the parent domain. This looks like it could lead to type confusion and out-of-bounds accesses on the domain structure.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Quoted 6 lines · Aneesh Kumar K.V
Quoted 6 lines
Currently,
amd_iommufd_get_viommu_ops()unconditionally returns the ops structure without checking the type argument. If a user requests an arbitrary type likeIOMMU_VIOMMU_TYPE_ARM_SMMUV3on an AMD device, the core iommufd framework might track the vIOMMU under a mismatched architectural type identifier.
Quoted 6 lines
Quoted 3 lines
The ARM and selftest backends appear to validate this argument against their supported hardware types before returning the operations.
No vIOMMU type has been added for any IOMMU other than Arm. The documentation also considers
IOMMU_VIOMMU_TYPE_DEFAULTreserved.
This is a bug in the AMD driver we need to fix, send a patch? It should only accept DEFAULT, not every type.
Quoted 15 lines
Quoted 15 lines
drivers/iommu/amd/iommufd.c static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu) · line 85
Quoted 8 lines
drivers/iommu/amd/iommufd.c +2
-0@@ -73,5 +79,7 @@ static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)* struct iommufd_viommu_ops - vIOMMU specific operations */ static const struct iommufd_viommu_ops amd_viommu_ops = { + .get_viommu_size = amd_iommufd_get_viommu_size, + .viommu_init = amd_iommufd_viommu_init, .destroy = amd_iommufd_viommu_destroy, };[Severity: High]
Is thealloc_domain_nestedcallback missing from this operations structure?The commit message and comments in
amd_iommu_alloc_domain_nested()indicate it should be assigned to the viommu operations. However, it is omitted here.
Quoted 7 lines
Quoted 4 lines
If a user attempts to allocate an AMD nested domain using
IOMMU_HWPT_ALLOC, won't the core framework reject it with-EOPNOTSUPPbecauseviommu->ops->alloc_domain_nestedis NULL?It is unclear whether nested domain support is fully integrated for AMD. I see
amd_iommu_alloc_domain_nested(), but cannot find any callers.
There is a series on the list to complete more of this, it is fine the way you have it.
Quoted 18 lines
Quoted 9 lines
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0[ ... ]
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0@@ -103,16 +105,12 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)*/ viommu->iommu_dev = iommu_dev;Quoted 8 lines
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) · line 110
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +2
-1- rc = ops->viommu_init (viommu, hwpt_paging->common.domain, + rc = ops->viommu_init (viommu, idev->dev, + hwpt_paging->common.domain, user_data.len ? &user_data :NULL );[Severity: Critical]
Is there a missing check to ensure the domain is owned by the same driver before passing it toviommu_init?
Quoted 2 lines
The
viommu_initcallback needs to perform this check. For example, the Arm implementation does:
No, the check is missing. If a domain is being used then the core code needs to check it, not the driver.
When the ops is first obtained it should be checked along with the domain to be sure it is compatible.
The NULL parent domain on the TSM path should be enforced in the core code since there is nothing to check there.
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Quoted 11 lines · Jason Gunthorpe
Quoted 10 lines · Aneesh Kumar K.V
Quoted 9 lines
Currently,
amd_iommufd_get_viommu_ops()unconditionally returns the ops structure without checking the type argument. If a user requests an arbitrary type likeIOMMU_VIOMMU_TYPE_ARM_SMMUV3on an AMD device, the core iommufd framework might track the vIOMMU under a mismatched architectural type identifier.The ARM and selftest backends appear to validate this argument against their supported hardware types before returning the operations.
Quoted 6 lines
Quoted 2 lines
No vIOMMU type has been added for any IOMMU other than Arm. The documentation also considers
IOMMU_VIOMMU_TYPE_DEFAULTreserved.This is a bug in the AMD driver we need to fix, send a patch? It should only accept DEFAULT, not every type.
But the comment says "Reserved"
* @IOMMU_VIOMMU_TYPE_DEFAULT: Reserved for future use
What is unclear is what using that type on Arm means. Does it imply IOMMU_VIOMMU_TYPE_ARM_SMMUV3?.
Quoted 49 lines
Quoted 16 lines
Quoted 16 lines
drivers/iommu/amd/iommufd.c static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu) · line 85
Quoted 9 lines
drivers/iommu/amd/iommufd.c +2
-0@@ -73,5 +79,7 @@ static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)* struct iommufd_viommu_ops - vIOMMU specific operations */ static const struct iommufd_viommu_ops amd_viommu_ops = { + .get_viommu_size = amd_iommufd_get_viommu_size, + .viommu_init = amd_iommufd_viommu_init, .destroy = amd_iommufd_viommu_destroy, };[Severity: High]
Is thealloc_domain_nestedcallback missing from this operations structure?The commit message and comments in
amd_iommu_alloc_domain_nested()indicate it should be assigned to the viommu operations. However, it is omitted here.Quoted 7 lines
Quoted 4 lines
If a user attempts to allocate an AMD nested domain using
IOMMU_HWPT_ALLOC, won't the core framework reject it with-EOPNOTSUPPbecauseviommu->ops->alloc_domain_nestedis NULL?It is unclear whether nested domain support is fully integrated for AMD. I see
amd_iommu_alloc_domain_nested(), but cannot find any callers.There is a series on the list to complete more of this, it is fine the way you have it.
Quoted 18 lines
Quoted 9 lines
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0[ ... ]
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0@@ -103,16 +105,12 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)*/ viommu->iommu_dev = iommu_dev;Quoted 8 lines
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) · line 110
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +2
-1- rc = ops->viommu_init (viommu, hwpt_paging->common.domain, + rc = ops->viommu_init (viommu, idev->dev, + hwpt_paging->common.domain, user_data.len ? &user_data :NULL );[Severity: Critical]
Is there a missing check to ensure the domain is owned by the same driver before passing it toviommu_init?Quoted 2 lines
The
viommu_initcallback needs to perform this check. For example, the Arm implementation does:
Quoted 9 lines
No, the check is missing. If a domain is being used then the core code needs to check it, not the driver.
When the ops is first obtained it should be checked along with the domain to be sure it is compatible.
The NULL parent domain on the TSM path should be enforced in the core code since there is nothing to check there.
So, if IOMMUFD_VIOMMU_NO_HWPT is not set, the HWPT must have been created with IOMMU_HWPT_ALLOC_NEST_PARENT, and its domain owner must match iommu_dev->ops?
Something like:
idev = iommufd_get_device(ucmd, cmd->dev_id);
iommu_dev = iommufd_device_get_iommu_dev(idev);
if (ops->flags & IOMMUFD_VIOMMU_NO_HWPT) {
if (cmd->hwpt_id) {
rc = -EINVAL;
goto out_put_idev;
}
} else {
hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
if (IS_ERR(hwpt_paging)) {
rc = PTR_ERR(hwpt_paging);
goto out_put_idev;
}
if (!hwpt_paging->nest_parent || !iommu_dev ||
hwpt_paging->common.domain->owner != iommu_dev->ops) {
rc = -EINVAL;
goto out_put_hwpt;
}
}-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Quoted 20 lines · Aneesh Kumar K.V
Quoted 11 lines · Jason Gunthorpe
Quoted 10 lines · Aneesh Kumar K.V
Quoted 9 lines
Currently,
amd_iommufd_get_viommu_ops()unconditionally returns the ops structure without checking the type argument. If a user requests an arbitrary type likeIOMMU_VIOMMU_TYPE_ARM_SMMUV3on an AMD device, the core iommufd framework might track the vIOMMU under a mismatched architectural type identifier.The ARM and selftest backends appear to validate this argument against their supported hardware types before returning the operations.
Quoted 6 lines
Quoted 2 lines
No vIOMMU type has been added for any IOMMU other than Arm. The documentation also considers
IOMMU_VIOMMU_TYPE_DEFAULTreserved.This is a bug in the AMD driver we need to fix, send a patch? It should only accept DEFAULT, not every type.
Quoted 6 lines
But the comment says "Reserved"
* @IOMMU_VIOMMU_TYPE_DEFAULT: Reserved for future use
What is unclear is what using that type on Arm means. Does it imply
IOMMU_VIOMMU_TYPE_ARM_SMMUV3?.
It fails
Probably thats the better answer, AMD should just always fail until they get their type added.
Quoted 27 lines
Quoted 27 lines
Quoted 18 lines
Quoted 9 lines
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0[ ... ]
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +0
-0@@ -103,16 +105,12 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)*/ viommu->iommu_dev = iommu_dev;Quoted 8 lines
drivers/iommu/iommufd/viommu.c int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) · line 110
Quoted 4 lines
drivers/iommu/iommufd/viommu.c +2
-1- rc = ops->viommu_init (viommu, hwpt_paging->common.domain, + rc = ops->viommu_init (viommu, idev->dev, + hwpt_paging->common.domain, user_data.len ? &user_data :NULL );[Severity: Critical]
Is there a missing check to ensure the domain is owned by the same driver before passing it toviommu_init?Quoted 2 lines
The
viommu_initcallback needs to perform this check. For example, the Arm implementation does:No, the check is missing. If a domain is being used then the core code needs to check it, not the driver.
When the ops is first obtained it should be checked along with the domain to be sure it is compatible.
Quoted 7 lines
Quoted 3 lines
The NULL parent domain on the TSM path should be enforced in the core code since there is nothing to check there.
So, if
IOMMUFD_VIOMMU_NO_HWPTis not set, the HWPT must have been created withIOMMU_HWPT_ALLOC_NEST_PARENT, and its domain owner must matchiommu_dev->ops?
And the tsm flow must have IOMMUFD_VIOMMU_NO_HWPT
Quoted 12 lines
Something like:
idev = iommufd_get_device(ucmd, cmd->dev_id); iommu_dev = iommufd_device_get_iommu_dev(idev); if (ops->flags & IOMMUFD_VIOMMU_NO_HWPT) { if (cmd->hwpt_id) { rc = -EINVAL; goto out_put_idev; } } else { hwpt_paging = iommufd_get_hwpt_paging(ucmd, cmd->hwpt_id);
Quoted 8 lines
if (IS_ERR(hwpt_paging)) {
rc = PTR_ERR(hwpt_paging);
goto out_put_idev;
}
if (!hwpt_paging->nest_parent || !iommu_dev ||
hwpt_paging->common.domain->owner != iommu_dev->ops) {
rc = -EINVAL;
goto out_put_hwpt;This only works if the ops came from a real iommu driver.
Jason
Jason Gunthorpe
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
In reply to Aneesh Kumar K.V (Arm)Quoted 11 lines
[ ... 62 lines skipped ... ]
drivers/iommu/amd/iommufd.c +2
-1@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_tyreturn hwinfo; } -size_t amd_iommufd_get_viommu_size (struct device *dev,enum iommu_viommu_type viommu_type) +static size_t amd_iommufd_get_viommu_size (struct device *dev, +enum iommu_viommu_type viommu_type) {
drivers/iommu/amd/iommufd.c void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty · line 45
Quoted 10 lines
drivers/iommu/amd/iommufd.c +5
-2return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core); } -int amd_iommufd_viommu_init (struct iommufd_viommu *viommu,struct iommu_domain *parent, -const struct iommu_user_data *user_data) +const struct iommufd_viommu_ops * +amd_iommufd_get_viommu_ops (struct device *dev,enum iommu_viommu_type viommu_type) +{ +return &amd_viommu_ops; +}
This needs to fail not retain the broken behavior if this is getting touched..
The rest looks OK and makes sense to me
Signature · 1 line
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Quoted 14 lines · Jason Gunthorpe
Quoted 14 lines
[ ... 62 lines skipped ... ]
drivers/iommu/amd/iommufd.c +2
-1@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_tyreturn hwinfo; } -size_t amd_iommufd_get_viommu_size (struct device *dev,enum iommu_viommu_type viommu_type) +static size_t amd_iommufd_get_viommu_size (struct device *dev, +enum iommu_viommu_type viommu_type) { return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core); }
Quoted 11 lines
drivers/iommu/amd/iommufd.c void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty · line 45
Quoted 7 lines
drivers/iommu/amd/iommufd.c +5
-2-int amd_iommufd_viommu_init (struct iommufd_viommu *viommu,struct iommu_domain *parent, -const struct iommu_user_data *user_data) +const struct iommufd_viommu_ops * +amd_iommufd_get_viommu_ops (struct device *dev,enum iommu_viommu_type viommu_type) +{ +return &amd_viommu_ops; +}This needs to fail not retain the broken behavior if this is getting touched..
Do you mean return NULL until we add an AMD-specific type equivalent to IOMMU_VIOMMU_TYPE_ARM_SMMUV3?
modified drivers/iommu/amd/iommufd.c
@@ -41,7 +41,7 @@ static size_t amd_iommufd_get_viommu_size(struct device *dev,
const struct iommufd_viommu_ops *
amd_iommufd_get_viommu_ops(struct device *dev, enum iommu_viommu_type viommu_type) {
- return &amd_viommu_ops;
+ return NULL:
}
int amd_iommufd_viommu_init(struct iommufd_viommu *viommu,
Alexey,
Could you look at this and suggest the AMD equivalent of arm_smmu_get_viommu_ops()? We could add it as part of this patch.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 13/16] iommufd/viommu: Select vIOMMU operations before allocation
Review static size_t amd_iommufd_get_viommu_size(struct device *dev, · line 47
Quoted 10 lines · Aneesh Kumar K.V
modified
drivers/iommu/amd/iommufd.c(quoted diff) +1
-1@@ -41,7 +41,7 @@ static size_t amd_iommufd_get_viommu_size(struct device *dev, const struct iommufd_viommu_ops * amd_iommufd_get_viommu_ops(struct device *dev, enum iommu_viommu_type viommu_type) { - return &amd_viommu_ops; + return NULL: } int amd_iommufd_viommu_init(struct iommufd_viommu *viommu,
Yes, it was a mistake to let this in the way it was
Ideally maybe lets get Joerg to pick up just that patch next week.
Quoted 4 lines
Alexey,
Could you look at this and suggest the AMD equivalent of
arm_smmu_get_viommu_ops()? We could add it as part of this patch.
It needs a whole other 20 patch series from Suravee to complete the viommu work in the driver.
https://patch.msgid.link/[email protected]
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 14/16 iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
In reply to Aneesh Kumar K.V (Arm)In confidential-computing configurations, a vIOMMU may be implemented by a TEE Security Manager rather than by the physical IOMMU driver. Such an implementation also needs the TSM identity when creating and validating vdevices.
Allow VIOMMU_ALLOC to first query the TSM associated with the target PCI device for vIOMMU operations. Fall back to the physical IOMMU driver when the TSM does not provide operations for the requested vIOMMU type.
Resolve the PCI TSM association under pci_tsm_rwsem and pin the module providing the operations. Store the TSM device in the vIOMMU so that the backend operations and TSM identity remain valid for its lifetime. Release the module pin when vIOMMU is destroyed.
This enables the Realm vIOMMU implementation to provide its operations through the PCI TSM backend and retain the TSM identity needed by its vdevices.
--- drivers/iommu/iommufd/viommu.c | 35 +++++++++++++++++++++++++++++----- drivers/pci/tsm.c | 21 ++++++++++++++++++++ drivers/virt/coco/tsm-core.c | 33 ++++++++++++++++++++++++++++++++ include/linux/iommufd.h | 2 ++ include/linux/pci-tsm.h | 22 +++++++++++++++++++++ include/linux/tsm.h | 23 ++++++++++++++++++++++ 6 files changed, 131 insertions(+), 5 deletions(-)
drivers/iommu/iommufd/viommu.c +30 -5
@@ -14,6 +14,8 @@ void iommufd_viommu_destroy(struct iommufd_object *obj)if (viommu->ops && viommu->ops->destroy) viommu->ops->destroy (viommu); +if (viommu->tsm_dev) +tsm_put_device (viommu->tsm_dev);refcount_dec (&viommu->hwpt->common.obj.users);if (viommu->kvm_file)fput (viommu->kvm_file);
@@ -29,6 +31,7 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) .len = cmd->data_len, };struct iommufd_hwpt_paging *hwpt_paging; +struct tsm_dev *tsm_dev =NULL ;struct iommufd_viommu *viommu;struct iommufd_device *idev;struct iommu_device *iommu_dev;
@@ -48,15 +51,33 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) rc = -EOPNOTSUPP ; goto out_put_idev; } -if (!iommu_dev->ops->get_viommu_ops) { - rc = -EOPNOTSUPP ; + tsm_dev =tsm_get_device (idev->dev); +if (IS_ERR (tsm_dev)) { + rc =PTR_ERR (tsm_dev); + tsm_dev =NULL ; goto out_put_idev; } - ops = iommu_dev->ops->get_viommu_ops (idev->dev, cmd->type); -if (!ops) { - rc = -EOPNOTSUPP ; + ops = tsm_dev ?tsm_viommu_get_ops (tsm_dev, idev->dev, cmd->type) :NULL ; +if (IS_ERR (ops)) { + rc =PTR_ERR (ops); goto out_put_idev; } +if (!ops) { +if (tsm_dev) { +tsm_put_device (tsm_dev); + tsm_dev =NULL ; + } +if (!iommu_dev->ops->get_viommu_ops) { + rc = -EOPNOTSUPP ; + goto out_put_idev; + } + ops = iommu_dev->ops->get_viommu_ops (idev->dev, cmd->type); +if (!ops) { + rc = -EOPNOTSUPP ; + goto out_put_idev; + } + } +/* * It is a driver bug to omit the required operations or provide a size * smaller than the core vIOMMU structure.
@@ -94,6 +115,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) viommu->kvm_file =get_file (idev->kvm_file); viommu->type = cmd->type; viommu->ictx = ucmd->ictx; + viommu->tsm_dev = tsm_dev; + tsm_dev =NULL ; viommu->hwpt = hwpt_paging;refcount_inc (&viommu->hwpt->common.obj.users);INIT_LIST_HEAD (&viommu->veventqs);
@@ -118,6 +141,8 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd) out_put_hwpt:iommufd_put_object (ucmd->ictx, &hwpt_paging->common.obj); out_put_idev: +if (tsm_dev) +tsm_put_device (tsm_dev);iommufd_put_object (ucmd->ictx, &idev->obj);return rc; }
drivers/pci/tsm.c +21 -0
@@ -9,6 +9,8 @@#define dev_fmt (fmt) "PCI/TSM: " fmt#include <linux/bitfield.h> +#include <linux/iommufd.h> +#include <linux/module.h> #include <linux/pci.h> #include <linux/pci-doe.h> #include <linux/pci-tsm.h>
@@ -36,6 +38,25 @@ static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)return tsm->tsm_dev->pci_ops; } +struct tsm_dev *pci_tsm_get_device (struct pci_dev *pdev) +{ +const struct pci_tsm_ops *ops; +struct tsm_dev *tsm_dev; + +guard (rwsem_read)(&pci_tsm_rwsem); +if (!pdev->tsm) +return NULL ; + + tsm_dev = pdev->tsm->tsm_dev; + ops = tsm_dev->pci_ops; +if (!ops->viommu_get_ops) +return NULL ; +if (!try_module_get (ops->owner)) +return ERR_PTR (-ENODEV ); +return tsm_dev; +} +EXPORT_SYMBOL_GPL (pci_tsm_get_device); +static inline bool is_dsm (struct pci_dev *pdev) {return pdev->tsm && pdev->tsm->dsm_dev == pdev;
drivers/virt/coco/tsm-core.c +33 -0
@@ -9,6 +9,16 @@#include <linux/cleanup.h> #include <linux/pci-tsm.h> +/* The caller must hold a tsm_get_device() reference for the entire use. */ +const struct iommufd_viommu_ops *tsm_viommu_get_ops (struct tsm_dev *tsm_dev, +struct device *dev,enum iommu_viommu_type type) +{ +if (!tsm_dev->pci_ops->viommu_get_ops) +return NULL ; +return tsm_dev->pci_ops->viommu_get_ops (dev, type); +} +EXPORT_SYMBOL_GPL (tsm_viommu_get_ops); +static void tsm_release (struct device *);static const struct class tsm_class = { .name ="tsm" ,
@@ -16,6 +26,29 @@ static const struct class tsm_class = { };static DEFINE_IDA (tsm_ida); +/** +* tsm_get_device() - Pin the TSM attached to a device +* @dev: device whose TSM is to be pinned +* +* A successful lookup pins the backend module. Backends providing vIOMMU +* operations must not unregister their TSM independently of module unload. +* Return: NULL if no vIOMMU-capable TSM is attached, an error if the module +* cannot be pinned, or the TSM device associated with the pinned backend. +*/ +struct tsm_dev *tsm_get_device (struct device *dev) +{ +if (!dev_is_pci (dev)) +return NULL ; +return pci_tsm_get_device (to_pci_dev (dev)); +} +EXPORT_SYMBOL_GPL (tsm_get_device); + +void tsm_put_device (struct tsm_dev *tsm_dev) +{ +module_put (tsm_dev->pci_ops->owner); +} +EXPORT_SYMBOL_GPL (tsm_put_device); +static int match_id (struct device *dev,const void *data) { struct tsm_dev *tsm_dev = container_of(dev, struct tsm_dev, dev);
include/linux/iommufd.h +2 -0
@@ -23,6 +23,7 @@ struct iommufd_access;struct iommufd_ctx ;struct iommufd_device ;struct iommufd_viommu_ops ; +struct tsm_dev ;struct page ;struct tsm_guest_req_info ;
@@ -105,6 +106,7 @@ struct iommufd_viommu {struct iommu_device *iommu_dev;struct iommufd_hwpt_paging *hwpt;struct file *kvm_file; +struct tsm_dev *tsm_dev;const struct iommufd_viommu_ops *ops;
include/linux/pci-tsm.h +22 -0
@@ -4,6 +4,8 @@#include <linux/mutex.h> #include <linux/pci.h> +struct iommufd_viommu_ops ; +struct module ;struct pci_tsm ;struct pci_tsm_context ;struct tsm_dev ;
@@ -16,12 +18,26 @@ struct tsm_dev;* @devsec_ops: Lock, unlock, and interrogate the security state of the * function via the platform TSM (typically virtual function * operations). +* @viommu_get_ops: Return operations for a supported TSM-owned vIOMMU type, +* or NULL if unsupported +* @owner: Module providing the vIOMMU operations, if built as a module +* +* The vIOMMU owner pins the backend module before querying @viommu_get_ops +* and releases it after vIOMMU destruction. A backend providing this callback +* must not unregister its TSM while the module is pinned. * * This operations are mutually exclusive either a tsm_dev instance * manages physical link properties or it manages function security * states like TDISP lock/unlock. +* +* @viommu_get_ops runs with the backend module pinned. The PCI/TSM +* association is checked during acquisition under pci_tsm_rwsem. */ struct pci_tsm_ops { +struct module *owner; +const struct iommufd_viommu_ops *(*viommu_get_ops)( +struct device *dev,enum iommu_viommu_type type); +/* * struct pci_tsm_link_ops - Manage physical link and the TSM/DSM session * @probe: establish context with the TSM (allocate / wrap 'struct
@@ -151,6 +167,7 @@ struct pci_tsm_pf0 *pci_tsm_context_pf0(struct pci_tsm_context *context);struct pci_dev *pci_tsm_context_dsm_dev (struct pci_tsm_context *context);bool pci_tsm_context_match_device (struct pci_tsm_context *context,struct pci_dev *pdev); +struct tsm_dev *pci_tsm_get_device (struct pci_dev *pdev);#else static inline int pci_tsm_register (struct tsm_dev *tsm_dev) {
@@ -165,6 +182,11 @@ static inline bool pci_tsm_is_configured(struct pci_dev *pdev)return false; } +static inline struct tsm_dev *pci_tsm_get_device (struct pci_dev *pdev) +{ +return NULL ; +} +static inline struct pci_tsm_context *pci_tsm_context_get (struct pci_dev *pdev,struct tsm_dev *viommu_tsm_dev) {
include/linux/tsm.h +23 -0
@@ -110,6 +110,8 @@ struct tsm_report_ops { };struct pci_tsm_ops ; +struct iommufd_viommu_ops ; +struct tsm_dev {struct device dev;int id;
@@ -126,6 +128,11 @@ void tsm_unregister(struct tsm_dev *tsm_dev);struct tsm_dev *find_tsm_dev (int id);#ifdef CONFIG_TSM +struct tsm_dev *tsm_get_device (struct device *dev); +void tsm_put_device (struct tsm_dev *tsm_dev); +const struct iommufd_viommu_ops *tsm_viommu_get_ops (struct tsm_dev *tsm_dev, +struct device *dev,enum iommu_viommu_type type); +/** * struct tsm_guest_req_info - parameters for a guest-initiated TSM request * @op: operation for the guest-initiated request
@@ -144,6 +151,22 @@ struct tsm_guest_req_info {u32 resp_len; };#else +static inline struct tsm_dev *tsm_get_device (struct device *dev) +{ +return NULL ; +} + +static inline void tsm_put_device (struct tsm_dev *tsm_dev) +{ +} + +static inline const struct iommufd_viommu_ops * +tsm_viommu_get_ops (struct tsm_dev *tsm_dev,struct device *dev, +enum iommu_viommu_type type) +{ +return NULL ; +} +struct tsm_guest_req_info ;#endif #endif /* __TSM_H */
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 14/16] iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] pci/tsm: Use-After-Free of tsm_dev due to missing device reference count --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] pci/tsm: Use-After-Free of tsm_dev due to missing device reference count --
commit 45c3cda47ad9fdb8464c2ba275fcaa5077625b75
Author: Aneesh Kumar K.V (Arm) <[email protected]>
iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
This commit allows a vIOMMU implementation to be provided by a TEE Security Manager (TSM) rather than the physical IOMMU driver. It queries the TSM associated with the target PCI device and resolves the PCI TSM association under pci_tsm_rwsem while pinning the module.
Quoted 4 lines
drivers/pci/tsm.c +0
-0
[ ... ]
Quoted 11 lines
drivers/pci/tsm.c +7
-0@@ -36,6 +38,25 @@ static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)return tsm->tsm_dev->pci_ops; } +struct tsm_dev *pci_tsm_get_device (struct pci_dev *pdev) +{ +const struct pci_tsm_ops *ops; +struct tsm_dev *tsm_dev; + +guard (rwsem_read)(&pci_tsm_rwsem); +if (!pdev->tsm)
drivers/pci/tsm.c static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm) · line 57
Quoted 10 lines
drivers/pci/tsm.c +10
-0+return NULL ; + + tsm_dev = pdev->tsm->tsm_dev; + ops = tsm_dev->pci_ops; +if (!ops->viommu_get_ops) +return NULL ; +if (!try_module_get (ops->owner)) +return ERR_PTR (-ENODEV ); +return tsm_dev; +}
[Severity: High]
Does this code need to take a kobject reference on the device via get_device(&tsm_dev->dev)?
The tsm_dev pointer is returned and stored in viommu->tsm_dev for the lifetime of the vIOMMU, relying solely on the module pin.
The doc comment for tsm_get_device() states that backends must not unregister their TSM independently of module unload. However, in the Linux device model, a module pin only prevents module removal; it does not prevent a PCI device from being unbound via sysfs (unbind or remove) or hardware hot-unplug.
If the device is unbound, the driver core forces the driver remove callback to run, which calls tsm_unregister().
Quoted 4 lines
drivers/virt/coco/tsm-core.c +0
-0
[ ... ]
Quoted 15 lines
drivers/virt/coco/tsm-core.c +11
-0@@ -16,6 +26,29 @@ static const struct class tsm_class = { };static DEFINE_IDA (tsm_ida); +/** +* tsm_get_device() - Pin the TSM attached to a device +* @dev: device whose TSM is to be pinned +* +* A successful lookup pins the backend module. Backends providing vIOMMU +* operations must not unregister their TSM independently of module unload. +* Return: NULL if no vIOMMU-capable TSM is attached, an error if the module +* cannot be pinned, or the TSM device associated with the pinned backend. +*/ +struct tsm_dev *tsm_get_device (struct device *dev) +{
drivers/virt/coco/tsm-core.c static const struct class tsm_class = { · line 49
Quoted 10 lines
drivers/virt/coco/tsm-core.c +10
-0+if (!dev_is_pci (dev)) +return NULL ; +return pci_tsm_get_device (to_pci_dev (dev)); +} +EXPORT_SYMBOL_GPL (tsm_get_device); + +void tsm_put_device (struct tsm_dev *tsm_dev) +{ +module_put (tsm_dev->pci_ops->owner); +}
[Severity: High]
Can this dereference freed memory?
When tsm_unregister() is called during an unbind or hot-unplug event, it invokes put_device(&tsm_dev->dev). Because no extra reference was held when acquiring the device in pci_tsm_get_device(), the reference count drops to zero and the tsm_dev object is freed via kfree().
Later, when the vIOMMU is destroyed, iommufd_viommu_destroy() calls tsm_put_device(viommu->tsm_dev). This dereferences the freed memory to access tsm_dev->pci_ops->owner, resulting in a use-after-free crash.
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=14
Aneesh Kumar K.V
maintainerRe: [PATCH v7 14/16] iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
Quoted 50 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] pci/tsm: Use-After-Free oftsm_devdue to missing device reference count --commit
45c3cda47ad9fdb8464c2ba275fcaa5077625b75
Author: Aneesh Kumar K.V (Arm) <[email protected]>iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
This commit allows a vIOMMU implementation to be provided by a TEE Security Manager (TSM) rather than the physical IOMMU driver. It queries the TSM associated with the target PCI device and resolves the PCI TSM association under
pci_tsm_rwsemwhile pinning the module.Quoted 4 lines
drivers/pci/tsm.c +0
-0[ ... ]
Quoted 11 lines
drivers/pci/tsm.c +7
-0@@ -36,6 +38,25 @@ static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)return tsm->tsm_dev->pci_ops; } +struct tsm_dev *pci_tsm_get_device (struct pci_dev *pdev) +{ +const struct pci_tsm_ops *ops; +struct tsm_dev *tsm_dev; + +guard (rwsem_read)(&pci_tsm_rwsem); +if (!pdev->tsm)drivers/pci/tsm.c static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm) · line 57
Quoted 10 lines
drivers/pci/tsm.c +10
-0+return NULL ; + + tsm_dev = pdev->tsm->tsm_dev; + ops = tsm_dev->pci_ops; +if (!ops->viommu_get_ops) +return NULL ; +if (!try_module_get (ops->owner)) +return ERR_PTR (-ENODEV ); +return tsm_dev; +}[Severity: High]
Does this code need to take a kobject reference on the device viaget_device(&tsm_dev->dev)?The
tsm_devpointer is returned and stored inviommu->tsm_devfor the lifetime of the vIOMMU, relying solely on the module pin.
Quoted 8 lines
The doc comment for
tsm_get_device()states that backends must not unregister their TSM independently of module unload. However, in the Linux device model, a module pin only prevents module removal; it does not prevent a PCI device from being unbound via sysfs (unbind or remove) or hardware hot-unplug.If the device is unbound, the driver core forces the driver remove callback to run, which calls
tsm_unregister().
An additional device reference is not strictly needed for the current CCA backend. arm-cca-host registers the TSM device, and the vIOMMU pins that module. Sysfs unbind is disabled, and the SMCCC device has no hot-unplug path, so the TSM remains registered while the vIOMMU exists.
But, taking a device reference makes it explicit that the vIOMMU owns a reference to the TSM device until it is destroyed. I'll add this in the next revision.
Quoted 37 lines
Quoted 5 lines
drivers/virt/coco/tsm-core.c +0
-0[ ... ]
Quoted 15 lines
drivers/virt/coco/tsm-core.c +11
-0@@ -16,6 +26,29 @@ static const struct class tsm_class = { };static DEFINE_IDA (tsm_ida); +/** +* tsm_get_device() - Pin the TSM attached to a device +* @dev: device whose TSM is to be pinned +* +* A successful lookup pins the backend module. Backends providing vIOMMU +* operations must not unregister their TSM independently of module unload. +* Return: NULL if no vIOMMU-capable TSM is attached, an error if the module +* cannot be pinned, or the TSM device associated with the pinned backend. +*/ +struct tsm_dev *tsm_get_device (struct device *dev) +{drivers/virt/coco/tsm-core.c static const struct class tsm_class = { · line 49
Quoted 10 lines
drivers/virt/coco/tsm-core.c +10
-0+if (!dev_is_pci (dev)) +return NULL ; +return pci_tsm_get_device (to_pci_dev (dev)); +} +EXPORT_SYMBOL_GPL (tsm_get_device); + +void tsm_put_device (struct tsm_dev *tsm_dev) +{ +module_put (tsm_dev->pci_ops->owner); +}[Severity: High]
Can this dereference freed memory?
Quoted 9 lines
When
tsm_unregister()is called during an unbind or hot-unplug event, it invokesput_device(&tsm_dev->dev). Because no extra reference was held when acquiring the device inpci_tsm_get_device(), the reference count drops to zero and thetsm_devobject is freed viakfree().Later, when the vIOMMU is destroyed,
iommufd_viommu_destroy()callstsm_put_device(viommu->tsm_dev). This dereferences the freed memory to accesstsm_dev->pci_ops->owner, resulting in a use-after-free crash.
This path is not reachable with the current CCA backend: sysfs unbind is disabled, the SMCCC device has no hot-unplug path, and the module pin prevents unload.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 14/16] iommufd/viommu: Allow PCI TSM backends to provide vIOMMU operations
In reply to Aneesh Kumar K.V (Arm)Quoted 7 lines
[ ... 178 lines skipped ... ]
drivers/virt/coco/tsm-core.c +6
-0+/** +* tsm_get_device() - Pin the TSM attached to a device +* @dev: device whose TSM is to be pinned +* +* A successful lookup pins the backend module. Backends providing vIOMMU +* operations must not unregister their TSM independently of module unload.
drivers/virt/coco/tsm-core.c static const struct class tsm_class = { · line 44
Quoted 10 lines
drivers/virt/coco/tsm-core.c +10
-0+* Return: NULL if no vIOMMU-capable TSM is attached, an error if the module +* cannot be pinned, or the TSM device associated with the pinned backend. +*/ +struct tsm_dev *tsm_get_device (struct device *dev) +{ +if (!dev_is_pci (dev)) +return NULL ; +return pci_tsm_get_device (to_pci_dev (dev)); +} +EXPORT_SYMBOL_GPL (tsm_get_device);
This maze of dead code and strange layering is pretty obfuscating, I hope we can delete it someday.
Aside from that, this looks OK.
I know Jacob and others were talking about needing to discover the driver from id using a lookup table like you did originaly, but I don't think tsm should use that if it does get created. It is simplifying to route things this way so the tsm struct is pinned down by the core code.
Signature · 1 line
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 15/16 iommufd: Allow vIOMMUs without a parent HWPT
In reply to Aneesh Kumar K.V (Arm)VIOMMU_ALLOC currently requires a nesting parent HWPT_PAGING even when the selected vIOMMU implementation has no use for one. This would force implementations such as the following Arm Realm vIOMMU to create an unused parent.
Add IOMMUFD_VIOMMU_NO_HWPT so an implementation can opt out. Require hwpt_id to be zero in that case and pass a NULL parent domain to its initialization callback. Keep the existing parent validation and reference handling for implementations that require a HWPT.
Reject nested domain and hardware queue allocation for a vIOMMU without a parent, and document the two allocation modes in the UAPI.
--- Documentation/userspace-api/iommufd.rst | 44 +++++++++---------- drivers/iommu/iommufd/hw_pagetable.c | 14 +++++-- drivers/iommu/iommufd/viommu.c | 56 +++++++++++++++---------- include/linux/iommufd.h | 13 ++++-- include/uapi/linux/iommufd.h | 3 +- 5 files changed, 77 insertions(+), 53 deletions(-)
Documentation/userspace-api/iommufd.rst +23 -21
@@ -82,11 +82,10 @@ Following IOMMUFD objects are exposed to userspace: * Direct assigned invalidation queues * Direct assigned interrupts - Such a vIOMMU object generally has the access to a nesting parent pagetable - to support some HW-accelerated virtualization features. So, a vIOMMU object - must be created given a nesting parent HWPT_PAGING object, and then it would - encapsulate that HWPT_PAGING object. Therefore, a vIOMMU object can be used - to allocate an HWPT_NESTED object in place of the encapsulated HWPT_PAGING. + A vIOMMU object may encapsulate a nesting parent HWPT_PAGING object to + support HW-accelerated virtualization features. Whether a parent is required + is determined by the selected vIOMMU implementation. Only a parent-backed + vIOMMU can be used to allocate an HWPT_NESTED object. .. note::
@@ -226,9 +225,9 @@ creating the objects and links:: flag is set. 4. IOMMUFD_OBJ_HWPT_NESTED can be only manually created via the IOMMU_HWPT_ALLOC - uAPI, provided an hwpt_id or a viommu_id of a vIOMMU object encapsulating a - nesting parent HWPT_PAGING via @pt_id to associate the new HWPT_NESTED object - to the corresponding HWPT_PAGING object. The associating HWPT_PAGING object + uAPI, provided an hwpt_id or a viommu_id of a parent-backed vIOMMU object + via @pt_id to associate the new HWPT_NESTED object to the corresponding + HWPT_PAGING object. The associating HWPT_PAGING object must be a nesting parent manually allocated via the same uAPI previously with an IOMMU_HWPT_ALLOC_NEST_PARENT flag, otherwise the allocation will fail. The allocation will be further validated by the IOMMU driver to ensure that the
@@ -245,25 +244,28 @@ creating the objects and links:: of the object passed in via the @pt_id field of struct iommufd_hwpt_alloc. 5. IOMMUFD_OBJ_VIOMMU can be only manually created via the IOMMU_VIOMMU_ALLOC - uAPI, provided a dev_id (for the device's physical IOMMU to back the vIOMMU) - and an hwpt_id (to associate the vIOMMU to a nesting parent HWPT_PAGING). The - iommufd core will link the vIOMMU object to the struct iommu_device that the - struct device is behind. And an IOMMU driver can implement a viommu_alloc op - to allocate its own vIOMMU data structure embedding the core-level structure - iommufd_viommu and some driver-specific data. If necessary, the driver can - also configure its HW virtualization feature for that vIOMMU (and thus for - the VM). Successful completion of this operation sets up the linkages between - the vIOMMU object and the HWPT_PAGING, then this vIOMMU object can be used - as a nesting parent object to allocate an HWPT_NESTED object described above. + uAPI, provided a dev_id identifying the device used to select the vIOMMU + implementation and, if the selected implementation requires a nesting + parent HWPT_PAGING, an hwpt_id. The hwpt_id must be zero for an + implementation that does not use a parent. A physical-IOMMU implementation + links the vIOMMU object to the struct iommu_device behind the device. Other + implementations, such as a TSM associated with the device, can provide their + own backing and device association. An implementation can allocate its own + vIOMMU data structure embedding the core-level structure iommufd_viommu and + some implementation-specific data. If necessary, it can also configure its + virtualization resources for that vIOMMU (and thus for the VM). A + parent-backed vIOMMU can be used as a nesting parent object to allocate an + HWPT_NESTED object described above. 6. IOMMUFD_OBJ_VDEVICE can be only manually created via the IOMMU_VDEVICE_ALLOC uAPI, provided a viommu_id for an iommufd_viommu object and a dev_id for an iommufd_device object. The vDEVICE object will be the binding between these two parent objects. Another @virt_id will be also set via the uAPI providing the iommufd core an index to store the vDEVICE object to a vDEVICE array per - vIOMMU. If necessary, the IOMMU driver may choose to implement a vdevce_alloc - op to init its HW for virtualization feature related to a vDEVICE. Successful - completion of this operation sets up the linkages between vIOMMU and device. + vIOMMU. A physical-IOMMU implementation requires the device to be behind the + same iommu_device as the vIOMMU. Other implementations validate the device + association while initializing the vDEVICE. Successful initialization sets + up the linkage between the vIOMMU and device. A device can only bind to an iommufd due to DMA ownership claim and attach to at most one IOAS object (no support of PASID yet).
drivers/iommu/iommufd/hw_pagetable.c +10 -4
@@ -306,6 +306,10 @@ iommufd_viommu_alloc_hwpt_nested(struct iommufd_viommu *viommu, u32 flags,return ERR_PTR (-EOPNOTSUPP );if (!user_data->len)return ERR_PTR (-EOPNOTSUPP ); +if (!viommu->hwpt) +return ERR_PTR (-EOPNOTSUPP ); +if (!viommu->iommu_dev) +return ERR_PTR (-EOPNOTSUPP );if (!viommu->ops || !viommu->ops->alloc_domain_nested)return ERR_PTR (-EOPNOTSUPP );
@@ -404,10 +408,12 @@ int iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd)struct iommufd_viommu *viommu; viommu = container_of(pt_obj, struct iommufd_viommu, obj); - iommu_dev =iommufd_device_get_iommu_dev (idev); -if (!iommu_dev || viommu->iommu_dev != iommu_dev) { - rc = -EINVAL ; - goto out_unlock; +if (viommu->iommu_dev) { + iommu_dev =iommufd_device_get_iommu_dev (idev); +if (!iommu_dev || viommu->iommu_dev != iommu_dev) { + rc = -EINVAL ; + goto out_unlock; + } } hwpt_nested =iommufd_viommu_alloc_hwpt_nested ( viommu, cmd->flags, &user_data);
drivers/iommu/iommufd/viommu.c +33 -23
121 lines not on this page · raw message
include/linux/iommufd.h +9 -4
@@ -6,6 +6,7 @@#ifndef __LINUX_IOMMUFD_H#define __LINUX_IOMMUFD_H +#include <linux/bits.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/iommu.h>
@@ -103,6 +104,7 @@ void iommufd_ctx_get(struct iommufd_ctx *ictx);struct iommufd_viommu {struct iommufd_object obj;struct iommufd_ctx *ictx; +/* Physical IOMMU backing, or NULL. */ struct iommu_device *iommu_dev;struct iommufd_hwpt_paging *hwpt;struct file *kvm_file;
@@ -150,17 +152,19 @@ struct iommufd_hw_queue {void (*destroy)(struct iommufd_hw_queue *hw_queue); }; +#define IOMMUFD_VIOMMU_NO_HWPT BIT(0) +/** * struct iommufd_viommu_ops - vIOMMU specific operations +* @flags: Properties needed by the core before vIOMMU initialization * @get_viommu_size: Get the size of a driver-level vIOMMU structure for a given * @dev corresponding to @viommu_type. Driver should return 0 * if vIOMMU isn't supported accordingly. It is required for * driver to use the VIOMMU_STRUCT_SIZE macro to sanitize the * driver-level vIOMMU structure related to the core one -* @viommu_init: Init the driver-level struct of an iommufd_viommu on a physical -* IOMMU instance @viommu->iommu_dev, as the set of virtualization -* resources shared/passed to user space IOMMU instance. Associate -* it with a nesting @parent_domain. +* @viommu_init: Initialize the selected driver-level iommufd_viommu for @dev. +* @parent_domain is the nesting parent, or NULL for an +* implementation that does not use a parent HWPT. * @destroy: Clean up all driver-specific parts of an iommufd_viommu. The memory * of the vIOMMU will be free-ed by iommufd core after calling this op * @alloc_domain_nested: Allocate a IOMMU_DOMAIN_NESTED on a vIOMMU that holds a
@@ -202,6 +206,7 @@ struct iommufd_hw_queue {* does, it should set it to the @hw_queue->destroy pointer */ struct iommufd_viommu_ops { +unsigned long flags;size_t (*get_viommu_size)(struct device *dev,enum iommu_viommu_type type); int (*viommu_init)(struct iommufd_viommu *viommu, struct device *dev,
include/uapi/linux/iommufd.h +2 -1
@@ -1130,7 +1130,8 @@ struct iommu_viommu_tegra241_cmdqv {* @flags: Must be 0 * @type: Type of the virtual IOMMU. Must be defined in enum iommu_viommu_type * @dev_id: The device's physical IOMMU will be used to back the virtual IOMMU -* @hwpt_id: ID of a nesting parent HWPT to associate to +* @hwpt_id: ID of a nesting parent HWPT to associate to. Must be zero when the +* selected vIOMMU implementation does not use a parent HWPT * @out_viommu_id: Output virtual IOMMU ID for the allocated object * @data_len: Length of the type specific data * @__reserved: Must be 0
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 15/16] iommufd: Allow vIOMMUs without a parent HWPT
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 13 lines
VIOMMU_ALLOCcurrently requires a nesting parentHWPT_PAGINGeven when the selected vIOMMU implementation has no use for one. This would force implementations such as the following Arm Realm vIOMMU to create an unused parent.Add
IOMMUFD_VIOMMU_NO_HWPTso an implementation can opt out. Requirehwpt_idto be zero in that case and pass a NULL parent domain to its initialization callback. Keep the existing parent validation and reference handling for implementations that require a HWPT.Reject nested domain and hardware queue allocation for a vIOMMU without a parent, and document the two allocation modes in the UAPI.
Quoted 9 lines
Sashiko has reviewed this patch and found no issues. It looks great!
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=15
Jason Gunthorpe
maintainerRe: [PATCH v7 15/16] iommufd: Allow vIOMMUs without a parent HWPT
In reply to Aneesh Kumar K.V (Arm)Quoted 22 lines
VIOMMU_ALLOCcurrently requires a nesting parentHWPT_PAGINGeven when the selected vIOMMU implementation has no use for one. This would force implementations such as the following Arm Realm vIOMMU to create an unused parent.Add
IOMMUFD_VIOMMU_NO_HWPTso an implementation can opt out. Requirehwpt_idto be zero in that case and pass a NULL parent domain to its initialization callback. Keep the existing parent validation and reference handling for implementations that require a HWPT.Reject nested domain and hardware queue allocation for a vIOMMU without a parent, and document the two allocation modes in the UAPI.
Quoted 8 lines
--- Documentation/userspace-api/iommufd.rst | 44 +++++++++---------- drivers/iommu/iommufd/hw_pagetable.c | 14 +++++-- drivers/iommu/iommufd/viommu.c | 56 +++++++++++++++---------- include/linux/iommufd.h | 13 ++++-- include/uapi/linux/iommufd.h | 3 +- 5 files changed, 77 insertions(+), 53 deletions(-)
I should check it carefully but this looks reasonable
Signature · 1 line
Jason
Aneesh Kumar K.V (Arm)
maintainerPATCH v7 16/16 PCI/TSM: wait for vdevice contexts before removing a DSM
In reply to Aneesh Kumar K.V (Arm)A PF0 DSM can be removed while a sibling function still has a vdevice. The context pins both the pci dev, but those references do not keep the PF0 DOE mailbox alive. Ignoring -EBUSY from link disconnect lets PCI continue to pci_doe_destroy(), leaving the vdevice with a stale mailbox pointer.
This follows the VFIO PCI removal model: vfio_unregister_group_dev() prevents new userspace opens and waits for existing users to release the device before teardown proceeds. Likewise, DSM removal rejects new contexts and waits for existing vdevice contexts to drain before destroying the DOE mailbox.
Mark the DSM as removing so no new contexts or subfunctions can attach. Wait for the last context to be released before disconnecting the link,
VFIO PCI also uses an eventfd to notify userspace that the device should be released. This patch does not add an equivalent notification for vdevice contexts; DSM removal waits for userspace to release them. Such a notification can be added later if required.
--- drivers/pci/tsm.c | 63 +++++++++++++++++++++++++++++++++++++++-- include/linux/pci-tsm.h | 5 ++++ 2 files changed, 65 insertions(+), 3 deletions(-)
drivers/pci/tsm.c +60 -3
@@ -10,10 +10,13 @@#include <linux/bitfield.h> #include <linux/iommufd.h> +#include <linux/jiffies.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/pci-doe.h> #include <linux/pci-tsm.h> +#include <linux/pid.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/sysfs.h> #include <linux/tsm.h>
@@ -354,6 +357,12 @@ struct pci_tsm_context *pci_tsm_context_get(struct pci_dev *pdev,return ERR_PTR (-ENOMEM );guard (mutex)(&pf0->lock); +if (pf0->removing) { +kfree (context); +return ERR_PTR (-ENODEV ); + } +if (!pf0->context_users) +reinit_completion (&pf0->contexts_drained); pf0->context_users++; context->pf0 = pf0; context->pdev =pci_dev_get (pdev);
@@ -368,8 +377,11 @@ void pci_tsm_context_put(struct pci_tsm_context *context)down_read (&pci_tsm_rwsem);mutex_lock (&pf0->lock); -if (!WARN_ON (!pf0->context_users)) - pf0->context_users--; +if (WARN_ON (!pf0->context_users)) + goto out_unlock; +if (!--pf0->context_users) +complete_all (&pf0->contexts_drained); +out_unlock:mutex_unlock (&pf0->lock);up_read (&pci_tsm_rwsem);
@@ -452,6 +464,9 @@ static ssize_t disconnect_store(struct device *dev, tsm_dev = pdev->tsm->tsm_dev;if (!sysfs_streq (buf,dev_name (&tsm_dev->dev)))return -EINVAL ; +if (is_link_tsm (tsm_dev) &&is_pci_tsm_pf0 (pdev) && +to_pci_tsm_pf0 (pdev->tsm)->removing) +return -ENODEV ; rc =pci_tsm_disconnect (pdev);if (rc)
@@ -663,6 +678,9 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm, struct tsm_dev *tsm_dev) {mutex_init (&tsm->lock); +init_completion (&tsm->contexts_drained); + tsm->context_users =0 ; + tsm->removing = false; tsm->doe_mb =pci_find_doe_mailbox (pdev,PCI_VENDOR_ID_PCI_SIG ,PCI_DOE_FEATURE_CMA );if (!tsm->doe_mb) {
@@ -751,8 +769,44 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)void pci_tsm_destroy (struct pci_dev *pdev) { -guard (rwsem_write)(&pci_tsm_rwsem); +struct pci_tsm_pf0 *pf0 =NULL ; +struct completion *drained; +bool interrupted = false; +long rc; + +down_write (&pci_tsm_rwsem); +if (pdev->tsm &&is_link_tsm (pdev->tsm->tsm_dev) && +is_pci_tsm_pf0 (pdev)) { + pf0 =to_pci_tsm_pf0 (pdev->tsm); + drained = &pf0->contexts_drained; +mutex_lock (&pf0->lock); + pf0->removing = true; +mutex_unlock (&pf0->lock); + +/* An unused DSM may never have completed contexts_drained. */ + rc = pf0->context_users ? +try_wait_for_completion (drained) :1 ; +/* Context release needs the read side of pci_tsm_rwsem. */ +up_write (&pci_tsm_rwsem); +while (rc <=0 ) { +if (interrupted) { + rc =wait_for_completion_timeout (drained,HZ *10 ); + }else { + rc =wait_for_completion_interruptible_timeout (drained, +HZ *10 ); +if (rc <0 ) { + interrupted = true; +pci_warn (pdev,"Task \"%s\" (%d) blocked until vdevices are released\n" , + current->comm,task_pid_nr (current)); + } + } +if (!rc) +pci_warn (pdev,"TSM connection is in use, waiting for vdevices\n" ); + } +down_write (&pci_tsm_rwsem); + }__pci_tsm_destroy (pdev,NULL ); +up_write (&pci_tsm_rwsem); }void pci_tsm_init (struct pci_dev *pdev)
@@ -779,6 +833,9 @@ void pci_tsm_init(struct pci_dev *pdev)*/ if (!dsm->tsm)return ; +if (is_link_tsm (dsm->tsm->tsm_dev) && +to_pci_tsm_pf0 (dsm->tsm)->removing) +return ;probe_fn (pdev, dsm); }
include/linux/pci-tsm.h +5 -0
@@ -1,6 +1,7 @@/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __PCI_TSM_H#define __PCI_TSM_H +#include <linux/completion.h> #include <linux/mutex.h> #include <linux/pci.h>
@@ -107,12 +108,16 @@ struct pci_tsm {* @lock: mutual exclustion for pci_tsm_ops invocation * @context_users: live per-function contexts on this PF0; a nonzero count * blocks link disconnect +* @contexts_drained: completed when the last context is released +* @removing: reject new contexts while the DSM is being removed * @doe_mb: PCIe Data Object Exchange mailbox */ struct pci_tsm_pf0 {struct pci_tsm base_tsm;struct mutex lock;unsigned int context_users; +struct completion contexts_drained; +bool removing;struct pci_doe_mb *doe_mb; };
Signature · 1 line
2.43.0
[email protected]
AI reviewRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] PCI/TSM: UAF due to bypassed drain wait in pci_tsm_unregister() --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] PCI/TSM: UAF due to bypassed drain wait in pci_tsm_unregister()
--
commit 70b40a7c8d41eb1bbe070cea07e7f252f47338e8
Author: Aneesh Kumar K.V (Arm) <[email protected]>
PCI/TSM: wait for vdevice contexts before removing a DSM
A PF0 DSM can be removed while a sibling function still has a vdevice. The context pins both the pci dev, but those references do not keep the PF0 DOE mailbox alive. Ignoring -EBUSY from link disconnect lets PCI continue to pci_doe_destroy(), leaving the vdevice with a stale mailbox pointer.
Quoted 4 lines
drivers/pci/tsm.c +0
-0
[ ... ]
Quoted 25 lines
drivers/pci/tsm.c +20
-1@@ -751,8 +769,44 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)void pci_tsm_destroy (struct pci_dev *pdev) { -guard (rwsem_write)(&pci_tsm_rwsem); +struct pci_tsm_pf0 *pf0 =NULL ; +struct completion *drained; +bool interrupted = false; +long rc; + +down_write (&pci_tsm_rwsem); +if (pdev->tsm &&is_link_tsm (pdev->tsm->tsm_dev) && +is_pci_tsm_pf0 (pdev)) { + pf0 =to_pci_tsm_pf0 (pdev->tsm); + drained = &pf0->contexts_drained; +mutex_lock (&pf0->lock); + pf0->removing = true; +mutex_unlock (&pf0->lock); + +/* An unused DSM may never have completed contexts_drained. */ + rc = pf0->context_users ? +try_wait_for_completion (drained) :1 ; +/* Context release needs the read side of pci_tsm_rwsem. */ +up_write (&pci_tsm_rwsem); +while (rc <=0 ) {
[ ... ]
drivers/pci/tsm.c static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev) · line 810
Quoted 8 lines
drivers/pci/tsm.c +6
-0+if (!rc) +pci_warn (pdev,"TSM connection is in use, waiting for vdevices\n" ); + } +down_write (&pci_tsm_rwsem); + }__pci_tsm_destroy (pdev,NULL ); +up_write (&pci_tsm_rwsem); }
[Severity: High]
This adds the context drain wait loop when the PCI device is destroyed, but what happens when the TSM provider device is unbound and calls pci_tsm_unregister() first?
Looking at pci_tsm_unregister() in drivers/pci/tsm.c, it directly calls __pci_tsm_destroy(pdev, tsm_dev) bypassing this new wait loop:
drivers/pci/tsm.c:pci_tsm_unregister() {
...
for_each_pci_dev_reverse(pdev)
__pci_tsm_destroy(pdev, tsm_dev);
}If contexts are active, __pci_tsm_destroy() skips clearing pdev->tsm because pci_tsm_disconnect() will return -EBUSY:
drivers/pci/tsm.c:__pci_tsm_destroy() {
...
if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev)) {
if (pci_tsm_disconnect(pdev))
pci_warn(pdev, "TSM connection is still in use\n");
} else {
...
}tsm_unregister() then drops its reference, and the provider frees the underlying pci_tsm memory. Later, when the PCI core calls this modified pci_tsm_destroy(), won't it dereference the already freed pdev->tsm and pdev->tsm->tsm_dev pointers here?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=16
Aneesh Kumar K.V
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Quoted 81 lines · [email protected] writes:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] PCI/TSM: UAF due to bypassed drain wait inpci_tsm_unregister()
--commit
70b40a7c8d41eb1bbe070cea07e7f252f47338e8
Author: Aneesh Kumar K.V (Arm) <[email protected]>PCI/TSM: wait for vdevice contexts before removing a DSM
A PF0 DSM can be removed while a sibling function still has a vdevice. The context pins both the pci dev, but those references do not keep the PF0 DOE mailbox alive. Ignoring
-EBUSYfrom link disconnect lets PCI continue topci_doe_destroy(), leaving the vdevice with a stale mailbox pointer.Quoted 4 lines
drivers/pci/tsm.c +0
-0[ ... ]
Quoted 25 lines
drivers/pci/tsm.c +20
-1@@ -751,8 +769,44 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)void pci_tsm_destroy (struct pci_dev *pdev) { -guard (rwsem_write)(&pci_tsm_rwsem); +struct pci_tsm_pf0 *pf0 =NULL ; +struct completion *drained; +bool interrupted = false; +long rc; + +down_write (&pci_tsm_rwsem); +if (pdev->tsm &&is_link_tsm (pdev->tsm->tsm_dev) && +is_pci_tsm_pf0 (pdev)) { + pf0 =to_pci_tsm_pf0 (pdev->tsm); + drained = &pf0->contexts_drained; +mutex_lock (&pf0->lock); + pf0->removing = true; +mutex_unlock (&pf0->lock); + +/* An unused DSM may never have completed contexts_drained. */ + rc = pf0->context_users ? +try_wait_for_completion (drained) :1 ; +/* Context release needs the read side of pci_tsm_rwsem. */ +up_write (&pci_tsm_rwsem); +while (rc <=0 ) {[ ... ]
drivers/pci/tsm.c static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev) · line 810
Quoted 8 lines
drivers/pci/tsm.c +6
-0+if (!rc) +pci_warn (pdev,"TSM connection is in use, waiting for vdevices\n" ); + } +down_write (&pci_tsm_rwsem); + }__pci_tsm_destroy (pdev,NULL ); +up_write (&pci_tsm_rwsem); }[Severity: High]
This adds the context drain wait loop when the PCI device is destroyed, but what happens when the TSM provider device is unbound and callspci_tsm_unregister()first?Looking at
pci_tsm_unregister()indrivers/pci/tsm.c, it directly calls__pci_tsm_destroy(pdev, tsm_dev)bypassing this new wait loop:drivers/pci/tsm.c:pci_tsm_unregister() { ... for_each_pci_dev_reverse(pdev) __pci_tsm_destroy(pdev, tsm_dev); }If contexts are active,
__pci_tsm_destroy()skips clearingpdev->tsmbecausepci_tsm_disconnect()will return-EBUSY:drivers/pci/tsm.c:__pci_tsm_destroy() { ... if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev)) { if (pci_tsm_disconnect(pdev)) pci_warn(pdev, "TSM connection is still in use\n"); } else { ... }
Quoted 5 lines
tsm_unregister()then drops its reference, and the provider frees the underlyingpci_tsmmemory. Later, when the PCI core calls this modifiedpci_tsm_destroy(), won't it dereference the already freedpdev->tsmandpdev->tsm->tsm_devpointers here?
With this series, vdevices hold a reference to their parent vIOMMU, which pins arm-cca-host. sysfs unbind is disabled, and the SMCCC provider device has no hot-unplug path.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Quoted 3 lines · Aneesh Kumar K.V
With this series, vdevices hold a reference to their parent vIOMMU, which pins arm-cca-host. sysfs unbind is disabled, and the SMCCC provider device has no hot-unplug path.
It probably needs some logic like vfio has to prevent unregister from completing if there are active users. The module pin should prevent it from ever triggering, but as a safety precaution.
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Quoted 10 lines · Jason Gunthorpe
Quoted 3 lines · Aneesh Kumar K.V
With this series, vdevices hold a reference to their parent vIOMMU, which pins arm-cca-host. sysfs unbind is disabled, and the SMCCC provider device has no hot-unplug path.
It probably needs some logic like vfio has to prevent unregister from completing if there are active users. The module pin should prevent it from ever triggering, but as a safety precaution.
Are you suggesting that tsm_unregister() should return an error or wait, as device removal does? Returning an error would be much easier to implement by adding an unregistering flag to tsm_dev, set under pci_tsm_rwsem after checking all pf0->context_users counters.
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Quoted 13 lines · Aneesh Kumar K.V
Quoted 10 lines · Jason Gunthorpe
Quoted 3 lines · Aneesh Kumar K.V
With this series, vdevices hold a reference to their parent vIOMMU, which pins arm-cca-host. sysfs unbind is disabled, and the SMCCC provider device has no hot-unplug path.
It probably needs some logic like vfio has to prevent unregister from completing if there are active users. The module pin should prevent it from ever triggering, but as a safety precaution.
Quoted 4 lines
Are you suggesting that
tsm_unregister()should return an error or wait, as device removal does? Returning an error would be much easier to implement by adding an unregistering flag totsm_dev, set underpci_tsm_rwsemafter checking allpf0->context_userscounters.
I didn't check it closely but the usual flow is for 'unregister' to wait until everything is done using it, possibly with a shootdown mechanism.
I'm not sure we can implement a sane shootdown for iommufd's consumption of the tsm, but we can at least do the wait.
Jason
Aneesh Kumar K.V
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Quoted 19 lines · Jason Gunthorpe
Quoted 13 lines · Aneesh Kumar K.V
Quoted 10 lines · Jason Gunthorpe
Quoted 3 lines · Aneesh Kumar K.V
With this series, vdevices hold a reference to their parent vIOMMU, which pins arm-cca-host. sysfs unbind is disabled, and the SMCCC provider device has no hot-unplug path.
It probably needs some logic like vfio has to prevent unregister from completing if there are active users. The module pin should prevent it from ever triggering, but as a safety precaution.
Quoted 4 lines
Are you suggesting that
tsm_unregister()should return an error or wait, as device removal does? Returning an error would be much easier to implement by adding an unregistering flag totsm_dev, set underpci_tsm_rwsemafter checking allpf0->context_userscounters.
Quoted 7 lines
I didn't check it closely but the usual flow is for 'unregister' to wait until everything is done using it, possibly with a shootdown mechanism.
I'm not sure we can implement a sane shootdown for iommufd's consumption of the tsm, but we can at least do the wait.
This will require.
@@ -116,6 +119,13 @@ struct tsm_dev {
struct device dev;
int id;
const struct pci_tsm_ops *pci_ops;
+ /* Serializes user acquisition against unregister */
+ struct mutex users_lock;
+ /* Written under users_lock, read with READ_ONCE() by bus code. */
+ bool unregistering;
+ /* Registration reference plus users that must finish before teardown. */
+ refcount_t users;
+ struct completion users_drained;
};
When a vIOMMU pins the tsm_dev, it must also increment users. This requires a new users_lock, since all existing locking is in pci_tsm.
I can add this as a separate patch, and then we can decide whether to keep it. I wonder whether we can avoid this complexity, since the vIOMMU already pins the TSM backend module and we expect unregister to be called only from module_exit().
-aneesh
Jason Gunthorpe
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
Quoted 45 lines · Aneesh Kumar K.V
Quoted 19 lines · Jason Gunthorpe
Quoted 13 lines · Aneesh Kumar K.V
Quoted 10 lines · Jason Gunthorpe
Quoted 3 lines · Aneesh Kumar K.V
With this series, vdevices hold a reference to their parent vIOMMU, which pins arm-cca-host. sysfs unbind is disabled, and the SMCCC provider device has no hot-unplug path.
It probably needs some logic like vfio has to prevent unregister from completing if there are active users. The module pin should prevent it from ever triggering, but as a safety precaution.
Quoted 4 lines
Are you suggesting that
tsm_unregister()should return an error or wait, as device removal does? Returning an error would be much easier to implement by adding an unregistering flag totsm_dev, set underpci_tsm_rwsemafter checking allpf0->context_userscounters.Quoted 7 lines
I didn't check it closely but the usual flow is for 'unregister' to wait until everything is done using it, possibly with a shootdown mechanism.
I'm not sure we can implement a sane shootdown for iommufd's consumption of the tsm, but we can at least do the wait.
This will require.
(quoted diff) +7
-0@@ -116,6 +119,13 @@ struct tsm_dev { struct device dev; int id; const struct pci_tsm_ops *pci_ops; + /* Serializes user acquisition against unregister */ + struct mutex users_lock; + /* Written under users_lock, read with READ_ONCE() by bus code. */ + bool unregistering; + /* Registration reference plus users that must finish before teardown. */ + refcount_t users; + struct completion users_drained; };
Quoted 2 lines
When a vIOMMU pins the
tsm_dev, it must also increment users. This requires a newusers_lock, since all existing locking is inpci_tsm.
I usually call this 'registration lock' and make it a rwsem.
Holding the read side of the 'regitration lock' means the driver cannot be unbound during the critical region.
Then ontop of that you use a refcount and completion.
refcount can only be incr'd while holding the registration lock.
Teardown decrs the refcount, holds the write side of the registration lock and waits for the completion. Read side is always try so it bails once the write side is taken, preventing new refcount incrs.
Most things use the registration lock, but long lived things would transition to the refcount.
Quoted 4 lines
I can add this as a separate patch, and then we can decide whether to keep it. I wonder whether we can avoid this complexity, since the vIOMMU already pins the TSM backend module and we expect unregister to be called only from
module_exit().
It is probably good practice to have a proper lifecycle scheme that does not only involve module refcounting. It will be easier to understand and use properly.
Jason
Re: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
In reply to Aneesh Kumar K.V (Arm)Quoted 5 lines · Aneesh Kumar K.V (Arm)
A PF0 DSM can be removed while a sibling function still has a vdevice. The context pins both the pci dev, but those references do not keep the PF0 DOE mailbox alive. Ignoring
-EBUSYfrom link disconnect lets PCI continue topci_doe_destroy(), leaving the vdevice with a stale mailbox pointer.
- Please capitalize the subject line to match history:
PCI/TSM: Wait for ...
- Please disambiguate "DSM" from the common "ACPI _DSM", e.g., by spelling out the first use of "DSM".
- "vdevice" is not a well-known term in drivers/pci/, so please clarify. The log messages may also benefit from more context, since sysadmins will be reading them. I assume this is not the pseries, xen, or fusion "vdevice", which are the current upstream uses.
Quoted 182 lines
This follows the VFIO PCI removal model:
vfio_unregister_group_dev()prevents new userspace opens and waits for existing users to release the device before teardown proceeds. Likewise, DSM removal rejects new contexts and waits for existing vdevice contexts to drain before destroying the DOE mailbox.Mark the DSM as removing so no new contexts or subfunctions can attach. Wait for the last context to be released before disconnecting the link,
VFIO PCI also uses an eventfd to notify userspace that the device should be released. This patch does not add an equivalent notification for vdevice contexts; DSM removal waits for userspace to release them. Such a notification can be added later if required.
Cc Bjorn Helgaas <[email protected]>Cc Jonathan Cameron <[email protected]>Cc Alexey Kardashevskiy <[email protected]>Cc Xu Yilun <[email protected]>Cc Lukas Wunner <[email protected]>Cc Samuel Ortiz <[email protected]>Cc Suzuki K Poulose <[email protected]>Cc Jason Gunthorpe <[email protected]>Cc Kevin Tian <[email protected]>Signed-off-by Aneesh Kumar K.V (Arm) <[email protected]>--- drivers/pci/tsm.c | 63 +++++++++++++++++++++++++++++++++++++++-- include/linux/pci-tsm.h | 5 ++++ 2 files changed, 65 insertions(+), 3 deletions(-)drivers/pci/tsm.c +60
-3@@ -10,10 +10,13 @@#include <linux/bitfield.h> #include <linux/iommufd.h> +#include <linux/jiffies.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/pci-doe.h> #include <linux/pci-tsm.h> +#include <linux/pid.h> +#include <linux/sched.h> #include <linux/slab.h> #include <linux/sysfs.h> #include <linux/tsm.h> @@ -354,6 +357,12 @@ struct pci_tsm_context *pci_tsm_context_get(struct pci_dev *pdev,return ERR_PTR (-ENOMEM );guard (mutex)(&pf0->lock); +if (pf0->removing) { +kfree (context); +return ERR_PTR (-ENODEV ); + } +if (!pf0->context_users) +reinit_completion (&pf0->contexts_drained); pf0->context_users++; context->pf0 = pf0; context->pdev =pci_dev_get (pdev);@@ -368,8 +377,11 @@ void pci_tsm_context_put(struct pci_tsm_context *context)down_read (&pci_tsm_rwsem);mutex_lock (&pf0->lock); -if (!WARN_ON (!pf0->context_users)) - pf0->context_users--; +if (WARN_ON (!pf0->context_users)) + goto out_unlock; +if (!--pf0->context_users) +complete_all (&pf0->contexts_drained); +out_unlock:mutex_unlock (&pf0->lock);up_read (&pci_tsm_rwsem);@@ -452,6 +464,9 @@ static ssize_t disconnect_store(struct device *dev, tsm_dev = pdev->tsm->tsm_dev;if (!sysfs_streq (buf,dev_name (&tsm_dev->dev)))return -EINVAL ; +if (is_link_tsm (tsm_dev) &&is_pci_tsm_pf0 (pdev) && +to_pci_tsm_pf0 (pdev->tsm)->removing) +return -ENODEV ; rc =pci_tsm_disconnect (pdev);if (rc)@@ -663,6 +678,9 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm, struct tsm_dev *tsm_dev) {mutex_init (&tsm->lock); +init_completion (&tsm->contexts_drained); + tsm->context_users =0 ; + tsm->removing = false; tsm->doe_mb =pci_find_doe_mailbox (pdev,PCI_VENDOR_ID_PCI_SIG ,PCI_DOE_FEATURE_CMA );if (!tsm->doe_mb) {@@ -751,8 +769,44 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)void pci_tsm_destroy (struct pci_dev *pdev) { -guard (rwsem_write)(&pci_tsm_rwsem); +struct pci_tsm_pf0 *pf0 =NULL ; +struct completion *drained; +bool interrupted = false; +long rc; + +down_write (&pci_tsm_rwsem); +if (pdev->tsm &&is_link_tsm (pdev->tsm->tsm_dev) && +is_pci_tsm_pf0 (pdev)) { + pf0 =to_pci_tsm_pf0 (pdev->tsm); + drained = &pf0->contexts_drained; +mutex_lock (&pf0->lock); + pf0->removing = true; +mutex_unlock (&pf0->lock); + +/* An unused DSM may never have completed contexts_drained. */ + rc = pf0->context_users ? +try_wait_for_completion (drained) :1 ; +/* Context release needs the read side of pci_tsm_rwsem. */ +up_write (&pci_tsm_rwsem); +while (rc <=0 ) { +if (interrupted) { + rc =wait_for_completion_timeout (drained,HZ *10 ); + }else { + rc =wait_for_completion_interruptible_timeout (drained, +HZ *10 ); +if (rc <0 ) { + interrupted = true; +pci_warn (pdev,"Task \"%s\" (%d) blocked until vdevices are released\n" , + current->comm,task_pid_nr (current)); + } + } +if (!rc) +pci_warn (pdev,"TSM connection is in use, waiting for vdevices\n" ); + } +down_write (&pci_tsm_rwsem); + }__pci_tsm_destroy (pdev,NULL ); +up_write (&pci_tsm_rwsem); }void pci_tsm_init (struct pci_dev *pdev)@@ -779,6 +833,9 @@ void pci_tsm_init(struct pci_dev *pdev)*/ if (!dsm->tsm)return ; +if (is_link_tsm (dsm->tsm->tsm_dev) && +to_pci_tsm_pf0 (dsm->tsm)->removing) +return ;probe_fn (pdev, dsm); }include/linux/pci-tsm.h +5
-0@@ -1,6 +1,7 @@/* SPDX-License-Identifier: GPL-2.0 */ #ifndef __PCI_TSM_H#define __PCI_TSM_H +#include <linux/completion.h> #include <linux/mutex.h> #include <linux/pci.h> @@ -107,12 +108,16 @@ struct pci_tsm {* @lock: mutual exclustion for pci_tsm_ops invocation * @context_users: live per-function contexts on this PF0; a nonzero count * blocks link disconnect +* @contexts_drained: completed when the last context is released +* @removing: reject new contexts while the DSM is being removed * @doe_mb: PCIe Data Object Exchange mailbox */ struct pci_tsm_pf0 {struct pci_tsm base_tsm;struct mutex lock;unsigned int context_users; +struct completion contexts_drained; +bool removing;struct pci_doe_mb *doe_mb; };Signature · 1 line
2.43.0
Jason Gunthorpe
maintainerRe: [PATCH v7 16/16] PCI/TSM: wait for vdevice contexts before removing a DSM
In reply to Aneesh Kumar K.V (Arm)Quoted 5 lines
A PF0 DSM can be removed while a sibling function still has a vdevice. The context pins both the pci dev, but those references do not keep the PF0 DOE mailbox alive. Ignoring
-EBUSYfrom link disconnect lets PCI continue topci_doe_destroy(), leaving the vdevice with a stale mailbox pointer.
Everytime PCI SIG puts something on PF0 that effects the entire MFD I think it is a mistake. This time is not an exception...
Quoted 6 lines
This follows the VFIO PCI removal model:
vfio_unregister_group_dev()prevents new userspace opens and waits for existing users to release the device before teardown proceeds. Likewise, DSM removal rejects new contexts and waits for existing vdevice contexts to drain before destroying the DOE mailbox.
I probably wouldn't focus on the vdevice so much here, it is really any user of the tsm framework in the kernel.
Quoted 7 lines
Mark the DSM as removing so no new contexts or subfunctions can attach. Wait for the last context to be released before disconnecting the link,
VFIO PCI also uses an eventfd to notify userspace that the device should be released. This patch does not add an equivalent notification for vdevice contexts; DSM removal waits for userspace to release them. Such a notification can be added later if required.
I ran out of time to read this closely, but the explanation make sense
Fix the sashiko things and lets see a v8 after the merge window opens and the first patches are picked up..
Signature · 1 line
Jason