[PATCH 3/7] vfio/pci: Introduce VF token

From: Alex Williamson
Date: Tue Feb 11 2020 - 18:05:53 EST


If we enable SR-IOV on a vfio-pci owned PF, the resulting VFs are not
fully isolated from the PF. The PF can always cause a denial of
service to the VF, if not access data passed through the VF directly.
This is why vfio-pci currently does not bind to PFs with SR-IOV enabled
and does not provide access itself to enabling SR-IOV on a PF. The
IOMMU grouping mechanism might allow us a solution to this lack of
isolation, however the deficiency isn't actually in the DMA path, so
much as the potential cooperation between PF and VF devices. Also,
if we were to force VFs into the same IOMMU group as the PF, we severely
limit the utility of having independent drivers managing PFs and VFs
with vfio.

Therefore we introduce the concept of a VF token. The token is
implemented as a UUID and represents a shared secret which must be set
by the PF driver and used by the VF drivers in order to access a vfio
device file descriptor for the VF. The ioctl to set the VF token will
be provided in a later commit, this commit implements the underlying
infrastructure. The concept here is to augment the string the user
passes to match a device within a group in order to retrieve access to
the device descriptor. For example, rather than passing only the PCI
device name (ex. "0000:03:00.0") the user would also pass a vf_token
UUID (ex. "2ab74924-c335-45f4-9b16-8569e5b08258"). The device match
string therefore becomes:

"0000:03:00.0 vf_token=2ab74924-c335-45f4-9b16-8569e5b08258"

This syntax is expected to be extensible to future options as well, with
the standard being:

"$DEVICE_NAME $OPTION1=$VALUE1 $OPTION2=$VALUE2"

The device name must be first and option=value pairs are separated by
spaces.

The vf_token option is only required for VFs where the PF device is
bound to vfio-pci. There is no change for PFs using existing host
drivers.

Note that in order to protect existing VF users, not only is it required
to set a vf_token on the PF before VFs devices can be accessed, but also
if there are existing VF users, (re)opening the PF device must also
provide the current vf_token as authentication. This is intended to
prevent a VF driver starting with a trusted PF driver and later being
replaced by an unknown driver. A vf_token is not required to open the
PF device when none of the VF devices are in use by vfio-pci drivers.

Signed-off-by: Alex Williamson <alex.williamson@xxxxxxxxxx>
---
drivers/vfio/pci/vfio_pci.c | 127 +++++++++++++++++++++++++++++++++++
drivers/vfio/pci/vfio_pci_private.h | 8 ++
2 files changed, 134 insertions(+), 1 deletion(-)

diff --git a/drivers/vfio/pci/vfio_pci.c b/drivers/vfio/pci/vfio_pci.c
index 2ec6c31d0ab0..26aea9ac4863 100644
--- a/drivers/vfio/pci/vfio_pci.c
+++ b/drivers/vfio/pci/vfio_pci.c
@@ -466,6 +466,35 @@ static void vfio_pci_disable(struct vfio_pci_device *vdev)
vfio_pci_set_power_state(vdev, PCI_D3hot);
}

+static struct pci_driver vfio_pci_driver;
+
+static void vfio_pci_vf_token_user_add(struct vfio_pci_device *vdev, int val)
+{
+ struct pci_dev *physfn = pci_physfn(vdev->pdev);
+ struct vfio_device *pf_dev;
+ struct vfio_pci_device *pf_vdev;
+
+ if (!vdev->pdev->is_virtfn)
+ return;
+
+ pf_dev = vfio_device_get_from_dev(&physfn->dev);
+ if (!pf_dev)
+ return;
+
+ if (pci_dev_driver(physfn) != &vfio_pci_driver) {
+ vfio_device_put(pf_dev);
+ return;
+ }
+
+ pf_vdev = vfio_device_data(pf_dev);
+
+ mutex_lock(&pf_vdev->vf_token->lock);
+ pf_vdev->vf_token->users += val;
+ mutex_unlock(&pf_vdev->vf_token->lock);
+
+ vfio_device_put(pf_dev);
+}
+
static void vfio_pci_release(void *device_data)
{
struct vfio_pci_device *vdev = device_data;
@@ -475,6 +504,7 @@ static void vfio_pci_release(void *device_data)
if (!(--vdev->refcnt)) {
vfio_spapr_pci_eeh_release(vdev->pdev);
vfio_pci_disable(vdev);
+ vfio_pci_vf_token_user_add(vdev, -1);
}

mutex_unlock(&vdev->reflck->lock);
@@ -493,6 +523,7 @@ static int vfio_pci_open(void *device_data)
mutex_lock(&vdev->reflck->lock);

if (!vdev->refcnt) {
+ vfio_pci_vf_token_user_add(vdev, 1);
ret = vfio_pci_enable(vdev);
if (ret)
goto error;
@@ -1278,11 +1309,86 @@ static void vfio_pci_request(void *device_data, unsigned int count)
mutex_unlock(&vdev->igate);
}

+#define VF_TOKEN_ARG "vf_token="
+
+/* Called with vdev->vf_token->lock */
+static int vfio_pci_vf_token_match(struct vfio_pci_device *vdev, char *opts)
+{
+ char *token;
+ uuid_t uuid;
+ int ret;
+
+ if (!opts)
+ return -EINVAL;
+
+ token = strstr(opts, VF_TOKEN_ARG);
+ if (!token)
+ return -EINVAL;
+
+ token += strlen(VF_TOKEN_ARG);
+
+ ret = uuid_parse(token, &uuid);
+ if (ret)
+ return ret;
+
+ if (!uuid_equal(&uuid, &vdev->vf_token->uuid))
+ return -EACCES;
+
+ return 0;
+}
+
static int vfio_pci_match(void *device_data, char *buf)
{
struct vfio_pci_device *vdev = device_data;
+ char *opts;
+
+ opts = strchr(buf, ' ');
+ if (opts) {
+ *opts = 0;
+ opts++;
+ }
+
+ if (strcmp(pci_name(vdev->pdev), buf))
+ return 0; /* No match */
+
+ if (vdev->pdev->is_virtfn) {
+ struct pci_dev *physfn = pci_physfn(vdev->pdev);
+ struct vfio_device *pf_dev;
+ int ret = 0;
+
+ pf_dev = vfio_device_get_from_dev(&physfn->dev);
+ if (pf_dev) {
+ if (pci_dev_driver(physfn) == &vfio_pci_driver) {
+ struct vfio_pci_device *pf_vdev =
+ vfio_device_data(pf_dev);
+
+ mutex_lock(&pf_vdev->vf_token->lock);
+ ret = vfio_pci_vf_token_match(pf_vdev, opts);
+ mutex_unlock(&pf_vdev->vf_token->lock);
+ }
+
+ vfio_device_put(pf_dev);
+
+ if (ret)
+ return -EACCES;
+ }
+ }

- return !strcmp(pci_name(vdev->pdev), buf);
+ if (vdev->vf_token) {
+ int ret = 0;
+
+ mutex_lock(&vdev->vf_token->lock);
+
+ if (vdev->vf_token->users)
+ ret = vfio_pci_vf_token_match(vdev, opts);
+
+ mutex_unlock(&vdev->vf_token->lock);
+
+ if (ret)
+ return -EACCES;
+ }
+
+ return 1; /* Match */
}

static const struct vfio_device_ops vfio_pci_ops = {
@@ -1354,6 +1460,19 @@ static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return ret;
}

+ if (pdev->is_physfn) {
+ vdev->vf_token = kzalloc(sizeof(*vdev->vf_token), GFP_KERNEL);
+ if (!vdev->vf_token) {
+ vfio_pci_reflck_put(vdev->reflck);
+ vfio_del_group_dev(&pdev->dev);
+ vfio_iommu_group_put(group, &pdev->dev);
+ kfree(vdev);
+ return -ENOMEM;
+ }
+ mutex_init(&vdev->vf_token->lock);
+ uuid_gen(&vdev->vf_token->uuid);
+ }
+
if (vfio_pci_is_vga(pdev)) {
vga_client_register(pdev, vdev, NULL, vfio_pci_set_vga_decode);
vga_set_legacy_decoding(pdev,
@@ -1387,6 +1506,12 @@ static void vfio_pci_remove(struct pci_dev *pdev)
if (!vdev)
return;

+ if (vdev->vf_token) {
+ WARN_ON(vdev->vf_token->users);
+ mutex_destroy(&vdev->vf_token->lock);
+ kfree(vdev->vf_token);
+ }
+
vfio_pci_reflck_put(vdev->reflck);

vfio_iommu_group_put(pdev->dev.iommu_group, &pdev->dev);
diff --git a/drivers/vfio/pci/vfio_pci_private.h b/drivers/vfio/pci/vfio_pci_private.h
index 8a2c7607d513..6da2b4748abf 100644
--- a/drivers/vfio/pci/vfio_pci_private.h
+++ b/drivers/vfio/pci/vfio_pci_private.h
@@ -12,6 +12,7 @@
#include <linux/pci.h>
#include <linux/irqbypass.h>
#include <linux/types.h>
+#include <linux/uuid.h>

#ifndef VFIO_PCI_PRIVATE_H
#define VFIO_PCI_PRIVATE_H
@@ -84,6 +85,12 @@ struct vfio_pci_reflck {
struct mutex lock;
};

+struct vfio_pci_vf_token {
+ struct mutex lock;
+ uuid_t uuid;
+ u32 users;
+};
+
struct vfio_pci_device {
struct pci_dev *pdev;
void __iomem *barmap[PCI_STD_NUM_BARS];
@@ -122,6 +129,7 @@ struct vfio_pci_device {
struct list_head dummy_resources_list;
struct mutex ioeventfds_lock;
struct list_head ioeventfds_list;
+ struct vfio_pci_vf_token *vf_token;
};

#define is_intx(vdev) (vdev->irq_type == VFIO_PCI_INTX_IRQ_INDEX)