iommu: Avoid more races around device probe
authorRobin Murphy <robin.murphy@arm.com>
Wed, 15 Nov 2023 18:25:44 +0000 (18:25 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 13 Dec 2023 17:39:17 +0000 (18:39 +0100)
commit a2e7e59a94269484a83386972ca07c22fd188854 upstream.

It turns out there are more subtle races beyond just the main part of
__iommu_probe_device() itself running in parallel - the dev_iommu_free()
on the way out of an unsuccessful probe can still manage to trip up
concurrent accesses to a device's fwspec. Thus, extend the scope of
iommu_probe_device_lock() to also serialise fwspec creation and initial
retrieval.

Reported-by: Zhenhua Huang <quic_zhenhuah@quicinc.com>
Link: https://lore.kernel.org/linux-iommu/e2e20e1c-6450-4ac5-9804-b0000acdf7de@quicinc.com/
Fixes: 01657bc14a39 ("iommu: Avoid races around device probe")
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: André Draszik <andre.draszik@linaro.org>
Tested-by: André Draszik <andre.draszik@linaro.org>
Link: https://lore.kernel.org/r/16f433658661d7cadfea51e7c65da95826112a2b.1700071477.git.robin.murphy@arm.com
Cc: stable@vger.kernel.org
Signed-off-by: Joerg Roedel <jroedel@suse.de>
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/acpi/scan.c
drivers/iommu/iommu.c
drivers/iommu/of_iommu.c
include/linux/iommu.h

index a0e347f6f97eb1d3c37964b238d5f707430353b8..94154a849a3ead0e30cd1f45b93990aecea7e5c9 100644 (file)
@@ -1563,17 +1563,22 @@ static const struct iommu_ops *acpi_iommu_configure_id(struct device *dev,
        int err;
        const struct iommu_ops *ops;
 
+       /* Serialise to make dev->iommu stable under our potential fwspec */
+       mutex_lock(&iommu_probe_device_lock);
        /*
         * If we already translated the fwspec there is nothing left to do,
         * return the iommu_ops.
         */
        ops = acpi_iommu_fwspec_ops(dev);
-       if (ops)
+       if (ops) {
+               mutex_unlock(&iommu_probe_device_lock);
                return ops;
+       }
 
        err = iort_iommu_configure_id(dev, id_in);
        if (err && err != -EPROBE_DEFER)
                err = viot_iommu_configure(dev);
+       mutex_unlock(&iommu_probe_device_lock);
 
        /*
         * If we have reason to believe the IOMMU driver missed the initial
index 2bcd1f23d07d27bd682416089e155ff98e860cce..8b38972394776c4ed9b5c15f10dad14417942770 100644 (file)
@@ -278,12 +278,13 @@ static void dev_iommu_free(struct device *dev)
        kfree(param);
 }
 
+DEFINE_MUTEX(iommu_probe_device_lock);
+
 static int __iommu_probe_device(struct device *dev, struct list_head *group_list)
 {
        const struct iommu_ops *ops = dev->bus->iommu_ops;
        struct iommu_device *iommu_dev;
        struct iommu_group *group;
-       static DEFINE_MUTEX(iommu_probe_device_lock);
        int ret;
 
        if (!ops)
@@ -295,11 +296,9 @@ static int __iommu_probe_device(struct device *dev, struct list_head *group_list
         * probably be able to use device_lock() here to minimise the scope,
         * but for now enforcing a simple global ordering is fine.
         */
-       mutex_lock(&iommu_probe_device_lock);
-       if (!dev_iommu_get(dev)) {
-               ret = -ENOMEM;
-               goto err_unlock;
-       }
+       lockdep_assert_held(&iommu_probe_device_lock);
+       if (!dev_iommu_get(dev))
+               return -ENOMEM;
 
        if (!try_module_get(ops->owner)) {
                ret = -EINVAL;
@@ -326,7 +325,6 @@ static int __iommu_probe_device(struct device *dev, struct list_head *group_list
        mutex_unlock(&group->mutex);
        iommu_group_put(group);
 
-       mutex_unlock(&iommu_probe_device_lock);
        iommu_device_link(iommu_dev, dev);
 
        return 0;
@@ -341,9 +339,6 @@ out_module_put:
 err_free:
        dev_iommu_free(dev);
 
-err_unlock:
-       mutex_unlock(&iommu_probe_device_lock);
-
        return ret;
 }
 
@@ -353,7 +348,9 @@ int iommu_probe_device(struct device *dev)
        struct iommu_group *group;
        int ret;
 
+       mutex_lock(&iommu_probe_device_lock);
        ret = __iommu_probe_device(dev, NULL);
+       mutex_unlock(&iommu_probe_device_lock);
        if (ret)
                goto err_out;
 
@@ -1684,7 +1681,9 @@ static int probe_iommu_group(struct device *dev, void *data)
                return 0;
        }
 
+       mutex_lock(&iommu_probe_device_lock);
        ret = __iommu_probe_device(dev, group_list);
+       mutex_unlock(&iommu_probe_device_lock);
        if (ret == -ENODEV)
                ret = 0;
 
index 5696314ae69e7d23b5ef7f71382bf95c5bda0bd7..1fa1db3be8529133c94be8971da6138eaec26198 100644 (file)
@@ -112,16 +112,20 @@ const struct iommu_ops *of_iommu_configure(struct device *dev,
                                           const u32 *id)
 {
        const struct iommu_ops *ops = NULL;
-       struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+       struct iommu_fwspec *fwspec;
        int err = NO_IOMMU;
 
        if (!master_np)
                return NULL;
 
+       /* Serialise to make dev->iommu stable under our potential fwspec */
+       mutex_lock(&iommu_probe_device_lock);
+       fwspec = dev_iommu_fwspec_get(dev);
        if (fwspec) {
-               if (fwspec->ops)
+               if (fwspec->ops) {
+                       mutex_unlock(&iommu_probe_device_lock);
                        return fwspec->ops;
-
+               }
                /* In the deferred case, start again from scratch */
                iommu_fwspec_free(dev);
        }
@@ -155,6 +159,8 @@ const struct iommu_ops *of_iommu_configure(struct device *dev,
                fwspec = dev_iommu_fwspec_get(dev);
                ops    = fwspec->ops;
        }
+       mutex_unlock(&iommu_probe_device_lock);
+
        /*
         * If we have reason to believe the IOMMU driver missed the initial
         * probe for dev, replay it to get things in order.
index 3c9da1f8979e3aaf0e2cd99a3637eb47c876d7d9..9d87090953bcc737e9bf514977c5d5d96dc7df3f 100644 (file)
@@ -657,6 +657,7 @@ static inline void dev_iommu_priv_set(struct device *dev, void *priv)
        dev->iommu->priv = priv;
 }
 
+extern struct mutex iommu_probe_device_lock;
 int iommu_probe_device(struct device *dev);
 void iommu_release_device(struct device *dev);