diff mbox series

[2/7] vfio/pci: Lock external INTx masking ops

Message ID 20240306211445.1856768-3-alex.williamson@redhat.com (mailing list archive)
State New, archived
Headers show
Series vfio: Interrupt eventfd hardening | expand

Commit Message

Alex Williamson March 6, 2024, 9:14 p.m. UTC
Mask operations through config space changes to DisINTx may race INTx
configuration changes via ioctl.  Create wrappers that add locking for
paths outside of the core interrupt code.

In particular, irq_type is updated holding igate, therefore testing
is_intx() requires holding igate.  For example clearing DisINTx from
config space can otherwise race changes of the interrupt configuration.

This aligns interfaces which may trigger the INTx eventfd into two
camps, one side serialized by igate and the other only enabled while
INTx is configured.  A subsequent patch introduces synchronization for
the latter flows.

Fixes: 89e1f7d4c66d ("vfio: Add PCI device driver")
Reported-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
---
 drivers/vfio/pci/vfio_pci_intrs.c | 34 +++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)

Comments

Tian, Kevin March 7, 2024, 8:37 a.m. UTC | #1
> From: Alex Williamson <alex.williamson@redhat.com>
> Sent: Thursday, March 7, 2024 5:15 AM
> 
> Mask operations through config space changes to DisINTx may race INTx
> configuration changes via ioctl.  Create wrappers that add locking for
> paths outside of the core interrupt code.
> 
> In particular, irq_type is updated holding igate, therefore testing
> is_intx() requires holding igate.  For example clearing DisINTx from
> config space can otherwise race changes of the interrupt configuration.
> 

Looks the suspend path still checks irq_type w/o holding igate:

	vdev->pm_intx_masked = ((vdev->irq_type == VFIO_PCI_INTX_IRQ_INDEX) &&
				 vfio_pci_intx_mask(vdev));

Is it with assumption that no change of configuration is possible at
that point?
Alex Williamson March 7, 2024, 8:21 p.m. UTC | #2
On Thu, 7 Mar 2024 08:37:53 +0000
"Tian, Kevin" <kevin.tian@intel.com> wrote:

> > From: Alex Williamson <alex.williamson@redhat.com>
> > Sent: Thursday, March 7, 2024 5:15 AM
> > 
> > Mask operations through config space changes to DisINTx may race INTx
> > configuration changes via ioctl.  Create wrappers that add locking for
> > paths outside of the core interrupt code.
> > 
> > In particular, irq_type is updated holding igate, therefore testing
> > is_intx() requires holding igate.  For example clearing DisINTx from
> > config space can otherwise race changes of the interrupt configuration.
> >   
> 
> Looks the suspend path still checks irq_type w/o holding igate:
> 
> 	vdev->pm_intx_masked = ((vdev->irq_type == VFIO_PCI_INTX_IRQ_INDEX) &&
> 				 vfio_pci_intx_mask(vdev));
> 
> Is it with assumption that no change of configuration is possible at
> that point?

Yes, I believe this is relatively safe because userspace is frozen at
this point.  That's not however to claim that irq_type is absolutely
used consistently after this series.  I just didn't see the other
violations rise to the same level as the fixes in this series and
wanted to avoid the distraction.  I've stashed a number of patches that
I'd eventually like to post as follow-ups to this series.  Thanks,

Alex
Tian, Kevin March 8, 2024, 7:17 a.m. UTC | #3
> From: Alex Williamson <alex.williamson@redhat.com>
> Sent: Friday, March 8, 2024 4:21 AM
> 
> On Thu, 7 Mar 2024 08:37:53 +0000
> "Tian, Kevin" <kevin.tian@intel.com> wrote:
> 
> > > From: Alex Williamson <alex.williamson@redhat.com>
> > > Sent: Thursday, March 7, 2024 5:15 AM
> > >
> > > Mask operations through config space changes to DisINTx may race INTx
> > > configuration changes via ioctl.  Create wrappers that add locking for
> > > paths outside of the core interrupt code.
> > >
> > > In particular, irq_type is updated holding igate, therefore testing
> > > is_intx() requires holding igate.  For example clearing DisINTx from
> > > config space can otherwise race changes of the interrupt configuration.
> > >
> >
> > Looks the suspend path still checks irq_type w/o holding igate:
> >
> > 	vdev->pm_intx_masked = ((vdev->irq_type ==
> VFIO_PCI_INTX_IRQ_INDEX) &&
> > 				 vfio_pci_intx_mask(vdev));
> >
> > Is it with assumption that no change of configuration is possible at
> > that point?
> 
> Yes, I believe this is relatively safe because userspace is frozen at
> this point.  That's not however to claim that irq_type is absolutely
> used consistently after this series.  I just didn't see the other
> violations rise to the same level as the fixes in this series and
> wanted to avoid the distraction.  I've stashed a number of patches that
> I'd eventually like to post as follow-ups to this series.  Thanks,
> 

Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Reinette Chatre March 8, 2024, 8:45 p.m. UTC | #4
Hi Alex,

On 3/6/2024 1:14 PM, Alex Williamson wrote:
> Mask operations through config space changes to DisINTx may race INTx
> configuration changes via ioctl.  Create wrappers that add locking for
> paths outside of the core interrupt code.
> 
> In particular, irq_type is updated holding igate, therefore testing
> is_intx() requires holding igate.  For example clearing DisINTx from
> config space can otherwise race changes of the interrupt configuration.
> 
> This aligns interfaces which may trigger the INTx eventfd into two
> camps, one side serialized by igate and the other only enabled while
> INTx is configured.  A subsequent patch introduces synchronization for
> the latter flows.
> 
> Fixes: 89e1f7d4c66d ("vfio: Add PCI device driver")
> Reported-by: Reinette Chatre <reinette.chatre@intel.com>
> Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
> ---

Thank you very much.

Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>

Reinette
diff mbox series

Patch

diff --git a/drivers/vfio/pci/vfio_pci_intrs.c b/drivers/vfio/pci/vfio_pci_intrs.c
index 136101179fcb..75c85eec21b3 100644
--- a/drivers/vfio/pci/vfio_pci_intrs.c
+++ b/drivers/vfio/pci/vfio_pci_intrs.c
@@ -99,13 +99,15 @@  static void vfio_send_intx_eventfd(void *opaque, void *unused)
 }
 
 /* Returns true if the INTx vfio_pci_irq_ctx.masked value is changed. */
-bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
+static bool __vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
 {
 	struct pci_dev *pdev = vdev->pdev;
 	struct vfio_pci_irq_ctx *ctx;
 	unsigned long flags;
 	bool masked_changed = false;
 
+	lockdep_assert_held(&vdev->igate);
+
 	spin_lock_irqsave(&vdev->irqlock, flags);
 
 	/*
@@ -143,6 +145,17 @@  bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
 	return masked_changed;
 }
 
+bool vfio_pci_intx_mask(struct vfio_pci_core_device *vdev)
+{
+	bool mask_changed;
+
+	mutex_lock(&vdev->igate);
+	mask_changed = __vfio_pci_intx_mask(vdev);
+	mutex_unlock(&vdev->igate);
+
+	return mask_changed;
+}
+
 /*
  * If this is triggered by an eventfd, we can't call eventfd_signal
  * or else we'll deadlock on the eventfd wait queue.  Return >0 when
@@ -194,12 +207,21 @@  static int vfio_pci_intx_unmask_handler(void *opaque, void *unused)
 	return ret;
 }
 
-void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
+static void __vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
 {
+	lockdep_assert_held(&vdev->igate);
+
 	if (vfio_pci_intx_unmask_handler(vdev, NULL) > 0)
 		vfio_send_intx_eventfd(vdev, NULL);
 }
 
+void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
+{
+	mutex_lock(&vdev->igate);
+	__vfio_pci_intx_unmask(vdev);
+	mutex_unlock(&vdev->igate);
+}
+
 static irqreturn_t vfio_intx_handler(int irq, void *dev_id)
 {
 	struct vfio_pci_core_device *vdev = dev_id;
@@ -563,11 +585,11 @@  static int vfio_pci_set_intx_unmask(struct vfio_pci_core_device *vdev,
 		return -EINVAL;
 
 	if (flags & VFIO_IRQ_SET_DATA_NONE) {
-		vfio_pci_intx_unmask(vdev);
+		__vfio_pci_intx_unmask(vdev);
 	} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
 		uint8_t unmask = *(uint8_t *)data;
 		if (unmask)
-			vfio_pci_intx_unmask(vdev);
+			__vfio_pci_intx_unmask(vdev);
 	} else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
 		struct vfio_pci_irq_ctx *ctx = vfio_irq_ctx_get(vdev, 0);
 		int32_t fd = *(int32_t *)data;
@@ -594,11 +616,11 @@  static int vfio_pci_set_intx_mask(struct vfio_pci_core_device *vdev,
 		return -EINVAL;
 
 	if (flags & VFIO_IRQ_SET_DATA_NONE) {
-		vfio_pci_intx_mask(vdev);
+		__vfio_pci_intx_mask(vdev);
 	} else if (flags & VFIO_IRQ_SET_DATA_BOOL) {
 		uint8_t mask = *(uint8_t *)data;
 		if (mask)
-			vfio_pci_intx_mask(vdev);
+			__vfio_pci_intx_mask(vdev);
 	} else if (flags & VFIO_IRQ_SET_DATA_EVENTFD) {
 		return -ENOTTY; /* XXX implement me */
 	}