From 258ba46543abdd36844f04bba8b4022a5731d873 Mon Sep 17 00:00:00 2001 From: Alex Williamson Date: Fri, 11 Sep 2026 11:04:24 -0600 Subject: [PATCH] vfio: Reject a second cdev open before mutating shared device state 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: LLM Reviewed-by: Jason Gunthorpe Signed-off-by: Alex Williamson Reviewed-by: Kevin Tian Link: https://lore.kernel.org/r/20260911170429.1642480-2-alex.williamson@nvidia.com Signed-off-by: Alex Williamson --- 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);