Skip to contentSkip to conversation

iommufd: vIOMMUs and TSM guest requests for confidential guests

PATCH In review Aneesh Kumar K.V (Arm) 8 participants 72 messages Oct 8 – Oct 30

Subsystems (18)
Documentation x86 KVM PCI +14 more subsystems
lore
Share & tools
lore

Aneesh Kumar K.V (Arm)

author maintainer
#0 lore raw

v7 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

Cc Alex Williamson <[email protected]>
Cc Alexey Kardashevskiy <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Catalin Marinas <[email protected]>
CC Jacob Pan <[email protected]>
Cc Jason Gunthorpe <[email protected]>
Cc Joerg Roedel <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jonathan Hunter <[email protected]>
Cc Kevin Tian <[email protected]>
Cc Krishna Reddy <[email protected]>
Cc Lukas Wunner <[email protected]>
Cc Nicolin Chen <[email protected]>
Cc Robin Murphy <[email protected]>
Cc Samuel Ortiz <[email protected]>
Cc Shameer Kolothum <[email protected]>
Cc Steven Price <[email protected]>
Cc Suravee Suthikulpanit <[email protected]>
Cc Suzuki K Poulose <[email protected]>
Cc Thierry Reding <[email protected]>
Cc Vasant Hegde <[email protected]>
Cc Will Deacon <[email protected]>
Cc Xu Yilun <[email protected]>

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
author maintainer
#1 lore raw

PATCH 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
index cd692f8fb764..8a7eed5847e1 100644
@@ -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
index 683bb8bf43a9..e460073ff176 100644
@@ -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
index 0474604ab8a1..e6d395ede9e2 100644
@@ -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
index cf7fe835c4ad..bc3abfde8393 100644
@@ -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
index 85f42289748d..07fc874a38e1 100644
@@ -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
#2 lore raw

Re: [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).

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
author maintainer
#3 lore raw

PATCH 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
index bc3abfde8393..3c5a389361c6 100644
@@ -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
index 07fc874a38e1..8e49ea809528 100644
@@ -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
#4 lore raw

Re: [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->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.

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
author maintainer
#5 lore raw

PATCH 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
index 63eb2155a774..db7ed11e9056 100644
@@ -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
#6 lore raw

Re: [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_data cast separately.

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
author maintainer
#7 lore raw

PATCH 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
index 8a7eed5847e1..9519b8028b10 100644
@@ -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
index 82892e1e03d9..d79bd3bdc68e 100644
@@ -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
index 3d040741044b..ea885dd5c9af 100644
@@ -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
index 7e8195a311bb..f12558dfcd81 100644
@@ -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
index 4db878c18f41..373ce574c431 100644
@@ -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
index b2299e5bc6df..5bf8cbdff377 100644
@@ -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
index 7728bc99b63d..9b619951a5d0 100644
@@ -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
index 45f08986359e..0cc91c6f96d2 100644
@@ -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
index 6cdc4e9a333a..19548a430942 100644
@@ -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
#8 lore raw

Re: [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
index 423ead48aafe3..ed96acfa86358 100644
@@ -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
author maintainer
#9 lore raw

PATCH 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
index e6d395ede9e2..c6bf463f5032 100644
@@ -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
index 8e49ea809528..ff4fab9753ca 100644
@@ -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
#10 lore raw

Re: [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, 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.

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
author maintainer
#11 lore raw

PATCH 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
index 3c5a389361c6..586f6e8bb81b 100644
@@ -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
index ff4fab9753ca..60e614795a36 100644
@@ -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_arch
 struct kvm *kvm_file_to_kvm_fn(struct file *file)
 {
Signature · 1 line
2.43.0
#12 lore raw

Re: [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_kvm function.

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
author maintainer
#13 lore raw

PATCH 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
index a664c70a6fe7..f99f7bee2e77 100644
@@ -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
index eb2e85b27e42..2560b4faa6f9 100644
@@ -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
index ee706f18f7e9..e9c825b24356 100644
@@ -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
index e9893d34d07b..3beb1b1bae36 100644
@@ -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
index 6e7efe83bc5d..0a0bb4abfbd2 100644
@@ -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
#14 lore raw

Re: [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
index a664c70a6fe73..f99f7bee2e774 100644

[ ... ]

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
author maintainer
#15 lore raw

Re: [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 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
index a664c70a6fe73..f99f7bee2e774 100644

[ ... ]

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);
}
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 freed kvm_file stored 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

maintainer
#16 lore raw

Re: [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 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
index a664c70a6fe73..f99f7bee2e774 100644

[ ... ]

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;
        }
        ...
}
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

author maintainer
#17 lore raw

PATCH 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]>

[nicolinc: hold kvm's users_count]
[aneesh.kumar: Switch to use kvm_file]
---
 drivers/iommu/iommufd/viommu.c | 5 +++++
 include/linux/iommufd.h        | 1 +
 2 files changed, 6 insertions(+)
drivers/iommu/iommufd/viommu.c +5 -0
index f7951057a1e5..b7489ed259bd 100644
@@ -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
index 0a0bb4abfbd2..3267717f676d 100644
@@ -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
#18 lore raw

Re: [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_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]>

Quoted 9 lines
[nicolinc: hold kvm's users_count]
[aneesh.kumar: Switch to use kvm_file]

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
#19 lore raw

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_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]>

[nicolinc: hold kvm's users_count]
[aneesh.kumar: Switch to use kvm_file]
---
 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.c index f7951057a1e5..b7489ed259bd 100644 --- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c

drivers/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 -0
 	viommu->type = cmd->type;
 	viommu->ictx = ucmd->ictx;
 	viommu->hwpt = hwpt_paging;
include/linux/iommufd.h +1 -0
index 0a0bb4abfbd2..3267717f676d 100644
@@ -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;
 
author maintainer
#20 lore raw

Re: [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_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]>

[nicolinc: hold kvm's users_count]
[aneesh.kumar: Switch to use kvm_file]
---
 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.c index f7951057a1e5..b7489ed259bd 100644 --- a/drivers/iommu/iommufd/viommu.c
+++ b/drivers/iommu/iommufd/viommu.c

drivers/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?

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

maintainer
#21 lore raw

RE: [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 PM

Quoted 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_ucmd

Quoted 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_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?

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...

#22 lore raw

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_file is NULL in iommufd_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

maintainer Reviewed-by on v7 8/16
#23 lore raw

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_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.

[...]

Signature · 1 line
Jason
author maintainer
#24 lore raw

PATCH 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
index e784993353d8..f79135986102 100644
@@ -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
#25 lore raw

Re: [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 -0
 	tsm_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 -2
 		dev_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
author maintainer
#26 lore raw

Re: [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 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 -0
 	tsm_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 -2
 		dev_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.

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

maintainer
#27 lore raw

Re: [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 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 -0
 	tsm_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 -2
 		dev_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 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 after device_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

author maintainer
#28 lore raw

Re: [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 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 -0
 	tsm_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 -2
 		dev_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 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 after device_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

maintainer
#29 lore raw

Re: [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

#30 lore raw

Re: [PATCH v7 09/16] tsm: Remove the device from lookup before PCI teardown

In reply to Aneesh Kumar K.V
Quoted 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 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 -0
 	tsm_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 -2
 		dev_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 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 after device_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 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.

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.

maintainer
#31 lore raw

Re: [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 -0
 	tsm_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 ... ]
void tsm_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
author maintainer
#32 lore raw

Re: [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_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?

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 -0
 	tsm_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.

Quoted 5 lines

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...

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

author maintainer
#33 lore raw

PATCH 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
index 67207914bb6e..f90efe2f9d10 100644
@@ -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
index 2560b4faa6f9..64e0d9e4da16 100644
@@ -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
index 9a921b153162..577eef2760a3 100644
@@ -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
index b7489ed259bd..f3d5b5a7eb4a 100644
@@ -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
index 3267717f676d..cfcf53b7c9e8 100644
@@ -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
index 381c53244c83..4bce4c9fa6f0 100644
@@ -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
#34 lore raw

Re: [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_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.

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
maintainer
#35 lore raw

Re: [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
author maintainer
#36 lore raw

Re: [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_MMIO why 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_STATE I 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_id be passed in as well, or we know it implicitly from the vdevice_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

maintainer
#37 lore raw

Re: [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_request callback 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_MMIO why 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_STATE I 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

maintainer
#38 lore raw

RE: [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 PM

Quoted 5 lines · Aneesh Kumar K.V
Quoted 3 lines · Jason Gunthorpe
Quoted 3 lines

[ ... 300 lines skipped ... ]
+/**
+ * enum iommu_vdevice_tsm_guest_req_op - Guest TSM request

operations

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 the

unlocked,

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 a

certificate,

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

+ * measurement, or interface report.
+ * @TSM_REQ_REGEN_OBJECT: Regenerate a device object, such as a

measurement 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 a

command.

Quoted 2 lines
Quoted 2 lines
Quoted 2 lines

+ *
+ * The request payload and response format depend on the TVM

architecture.

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_MMIO why 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_STATE I 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...

maintainer
#39 lore raw

Re: [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_MMIO why 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

author maintainer
#40 lore raw

PATCH 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
index 5fdcd7f2e820..1423c64dc9c8 100644
@@ -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
#41 lore raw

Re: [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
maintainer Reviewed-by on v7 11/16
#42 lore raw

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
author maintainer
#43 lore raw

PATCH 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
index 55ea1db749a1..4c64b30544cd 100644
@@ -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
#44 lore raw

Re: [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_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.

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
maintainer
#45 lore raw

Re: [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_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 +0 -0
index 55ea1db749a180..4c64b30544cdca 100644

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
author maintainer
#46 lore raw

PATCH 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
index 56262f6b1f70..a3c58dd39549 100644
@@ -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
index 52300b867c1f..c1e132382adc 100644
@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty
 	return 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
index 62e9e1bebfbe..0d8e7c0902cf 100644
@@ -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
index f1c7987fc585..5b07136e7cd9 100644
@@ -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
index 25982bdbcbd9..1dd103196353 100644
@@ -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
index 5732f3ba0122..97dfaec6dc58 100644
@@ -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
index dd2fee2f560e..79f2adf7a1f5 100644
@@ -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
index 6644075c1431..1c8a7939fc1c 100644
@@ -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
index e9c825b24356..426ea94467ff 100644
@@ -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
index f3d5b5a7eb4a..e95138ae71d5 100644
@@ -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
index ac43b8b93f14..9931c96e5dd3 100644
@@ -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
index cfcf53b7c9e8..98ac3e5e9c66 100644
@@ -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
#47 lore raw

Re: [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
index 52300b867c1f2..c1e132382adc7 100644
@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty
 	return 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 -2
 	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;
+}

[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
index f3d5b5a7eb4a4..e95138ae71d5b 100644

[ ... ]

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
author maintainer
#48 lore raw

Re: [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 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
index 52300b867c1f2..c1e132382adc7 100644
@@ -32,13 +32,21 @@ void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_ty
 	return 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 -2
 	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;
+}

[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.

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 the alloc_domain_nested callback 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 -EOPNOTSUPP because viommu->ops->alloc_domain_nested is 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
index f3d5b5a7eb4a4..e95138ae71d5b 100644

[ ... ]

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 to viommu_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.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.

-aneesh

maintainer
#49 lore raw

Re: [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 like IOMMU_VIOMMU_TYPE_ARM_SMMUV3 on 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_DEFAULT reserved.

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 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.

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 -EOPNOTSUPP because viommu->ops->alloc_domain_nested is 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
index f3d5b5a7eb4a4..e95138ae71d5b 100644

[ ... ]

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 to viommu_init?

Quoted 2 lines

The viommu_init callback 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

author maintainer
#50 lore raw

Re: [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 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.

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_DEFAULT reserved.

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 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.

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 -EOPNOTSUPP because viommu->ops->alloc_domain_nested is 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
index f3d5b5a7eb4a4..e95138ae71d5b 100644

[ ... ]

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 to viommu_init?

Quoted 2 lines

The viommu_init callback 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

maintainer
#51 lore raw

Re: [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 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.

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_DEFAULT reserved.

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
index f3d5b5a7eb4a4..e95138ae71d5b 100644

[ ... ]

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 to viommu_init?

Quoted 2 lines

The viommu_init callback 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_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?

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

maintainer
#52 lore raw

Re: [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_ty
 	return 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 -2
 	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;
+}

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
author maintainer
#53 lore raw

Re: [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_ty
 	return 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

maintainer
#54 lore raw

Re: [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

author maintainer
#55 lore raw

PATCH 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
index e95138ae71d5..8628161b37c6 100644
@@ -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
index dca9634f8d5e..c8603867e09d 100644
@@ -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
index f79135986102..2fce341ff923 100644
@@ -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
index 98ac3e5e9c66..fd4567940243 100644
@@ -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
index eaf0199d01f7..b27f7cf99f22 100644
@@ -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
index 4bce4c9fa6f0..e5d14f9a23e9 100644
@@ -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
#56 lore raw

Re: [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
index dca9634f8d5eb..c8603867e09df 100644

[ ... ]

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
index f791359861020..2fce341ff9231 100644

[ ... ]

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
author maintainer
#57 lore raw

Re: [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 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
index dca9634f8d5eb..c8603867e09df 100644

[ ... ]

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.

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
index f791359861020..2fce341ff9231 100644

[ ... ]

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 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.

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

maintainer
#58 lore raw

Re: [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
author maintainer
#59 lore raw

PATCH 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
index f1c4d21e5c5e..8e2fa3a524f0 100644
@@ -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
index ef6e119c2a75..bd562bc2db48 100644
@@ -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
index fd4567940243..2e67846d6f35 100644
@@ -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
index 9920138f9eda..3d1ebbdf3948 100644
@@ -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
#60 lore raw

Re: [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_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.

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
maintainer
#61 lore raw

Re: [PATCH v7 15/16] iommufd: Allow vIOMMUs without a parent HWPT

In reply to Aneesh Kumar K.V (Arm)
Quoted 22 lines

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.

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
author maintainer
#62 lore raw

PATCH 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.

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]>
---
 drivers/pci/tsm.c       | 63 +++++++++++++++++++++++++++++++++++++++--
 include/linux/pci-tsm.h |  5 ++++
 2 files changed, 65 insertions(+), 3 deletions(-)
drivers/pci/tsm.c +60 -3
index c8603867e09d..8aa74ba31932 100644
@@ -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
index b27f7cf99f22..3adc317d0f9b 100644
@@ -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
#63 lore raw

Re: [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
index c8603867e09df..8aa74ba31932b 100644

[ ... ]

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
author maintainer
#64 lore raw

Re: [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 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
index c8603867e09df..8aa74ba31932b 100644

[ ... ]

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 {
    ...
}
Quoted 5 lines

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?

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

maintainer
#65 lore raw

Re: [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

author maintainer
#66 lore raw

Re: [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

maintainer
#67 lore raw

Re: [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 to tsm_dev, set under pci_tsm_rwsem after checking all pf0->context_users counters.

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

author maintainer
#68 lore raw

Re: [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 to tsm_dev, set under pci_tsm_rwsem after checking all pf0->context_users counters.

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

maintainer
#69 lore raw

Re: [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 to tsm_dev, set under pci_tsm_rwsem after checking all pf0->context_users counters.

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 new users_lock, since all existing locking is in pci_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

#70 lore raw

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 -EBUSY from link disconnect lets PCI continue to pci_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]>
---
 drivers/pci/tsm.c       | 63 +++++++++++++++++++++++++++++++++++++++--
 include/linux/pci-tsm.h |  5 ++++
 2 files changed, 65 insertions(+), 3 deletions(-)
drivers/pci/tsm.c +60 -3
index c8603867e09d..8aa74ba31932 100644
@@ -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
index b27f7cf99f22..3adc317d0f9b 100644
@@ -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
maintainer
#71 lore raw

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

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.

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