[PATCH 1/4] vfio: Reject a second cdev open before mutating shared device state

From: Alex Williamson

Date: Tue Sep 01 2026 - 18:01:35 EST


The cdev single-open check lives in vfio_df_open(), which runs at the
end of the bind ioctl, after vfio_df_ioctl_bind_iommufd() has already
updated state shared across all opens: vfio_df_check_token() can set
the PF vf_token and vfio_df_get_kvm_safe() records the caller's KVM
pointer in device->kvm and takes a reference.

A second cdev bind of an already-open device runs both, only to be
rejected in vfio_df_open(). The error path clears device->kvm and
drops the reference, tearing down the current opener's KVM association
and potentially resulting in an unbalanced reference on close or
premature release, while the vf_token remains clobbered.

Move the single-open check into vfio_df_ioctl_bind_iommufd() ahead of
both mutations, so a bind that cannot complete leaves the current
opener's state untouched. df->group is NULL on this path, so a
non-zero open_count is exactly what vfio_df_open() rejected. The test
in vfio_df_open() becomes redundant and is removed.

Return -EBUSY rather than -EINVAL here. The arguments are not invalid,
the device is in use, which could be a transient condition due to a
delayed fput if the prior user is terminated. This provides
compatibility with the group path, where a group open returns -EBUSY,
and users may choose bounded polling to detect such a transient
condition.

Fixes: 839e692fa4eb ("vfio: Make vfio_df_open() single open for device cdev path")
Fixes: 5fcc26969a16 ("vfio: Add VFIO_DEVICE_BIND_IOMMUFD")
Fixes: 86624ba3b522 ("vfio/pci: Do vf_token checks for VFIO_DEVICE_BIND_IOMMUFD")
Assisted-by: claude-opus-4-8
Signed-off-by: Alex Williamson <alex.williamson@xxxxxxxxxx>
---
drivers/vfio/device_cdev.c | 12 ++++++++++++
drivers/vfio/vfio_main.c | 7 -------
2 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/drivers/vfio/device_cdev.c b/drivers/vfio/device_cdev.c
index 1d9515c967b0..30362936c7b5 100644
--- a/drivers/vfio/device_cdev.c
+++ b/drivers/vfio/device_cdev.c
@@ -130,6 +130,18 @@ long vfio_df_ioctl_bind_iommufd(struct vfio_device_file *df,
goto out_unlock;
}

+ /*
+ * The cdev path allows only a single open. Reject a second open here,
+ * before the VF token and device->kvm updates below would clobber the
+ * current opener's state on a bind that cannot complete. Return -EBUSY
+ * rather than -EINVAL since a delayed release of the prior opener can
+ * make this transient.
+ */
+ if (device->open_count) {
+ ret = -EBUSY;
+ goto out_unlock;
+ }
+
ret = vfio_df_check_token(device, &bind);
if (ret)
goto out_unlock;
diff --git a/drivers/vfio/vfio_main.c b/drivers/vfio/vfio_main.c
index 423ead48aafe..cb3deb5a4857 100644
--- a/drivers/vfio/vfio_main.c
+++ b/drivers/vfio/vfio_main.c
@@ -603,13 +603,6 @@ int vfio_df_open(struct vfio_device_file *df)

lockdep_assert_held(&device->dev_set->lock);

- /*
- * Only the group path allows the device to be opened multiple
- * times. The device cdev path doesn't have a secure way for it.
- */
- if (device->open_count != 0 && !df->group)
- return -EINVAL;
-
device->open_count++;
if (device->open_count == 1) {
ret = vfio_df_device_first_open(df);
--
2.53.0