]> git.ipfire.org Git - thirdparty/linux.git/blobdiff - drivers/usb/core/port.c
USB: core: Fix deadlock in port "disable" sysfs attribute
[thirdparty/linux.git] / drivers / usb / core / port.c
index 5b5e613a11e599bdb05dd121ad073f69a7f5b386..686c01af03e63aa52253be6c44371ac335eb8fcd 100644 (file)
@@ -56,11 +56,22 @@ static ssize_t disable_show(struct device *dev,
        u16 portstatus, unused;
        bool disabled;
        int rc;
+       struct kernfs_node *kn;
 
+       hub_get(hub);
        rc = usb_autopm_get_interface(intf);
        if (rc < 0)
-               return rc;
+               goto out_hub_get;
 
+       /*
+        * Prevent deadlock if another process is concurrently
+        * trying to unregister hdev.
+        */
+       kn = sysfs_break_active_protection(&dev->kobj, &attr->attr);
+       if (!kn) {
+               rc = -ENODEV;
+               goto out_autopm;
+       }
        usb_lock_device(hdev);
        if (hub->disconnected) {
                rc = -ENODEV;
@@ -70,9 +81,13 @@ static ssize_t disable_show(struct device *dev,
        usb_hub_port_status(hub, port1, &portstatus, &unused);
        disabled = !usb_port_is_power_on(hub, portstatus);
 
-out_hdev_lock:
+ out_hdev_lock:
        usb_unlock_device(hdev);
+       sysfs_unbreak_active_protection(kn);
+ out_autopm:
        usb_autopm_put_interface(intf);
+ out_hub_get:
+       hub_put(hub);
 
        if (rc)
                return rc;
@@ -90,15 +105,26 @@ static ssize_t disable_store(struct device *dev, struct device_attribute *attr,
        int port1 = port_dev->portnum;
        bool disabled;
        int rc;
+       struct kernfs_node *kn;
 
        rc = kstrtobool(buf, &disabled);
        if (rc)
                return rc;
 
+       hub_get(hub);
        rc = usb_autopm_get_interface(intf);
        if (rc < 0)
-               return rc;
+               goto out_hub_get;
 
+       /*
+        * Prevent deadlock if another process is concurrently
+        * trying to unregister hdev.
+        */
+       kn = sysfs_break_active_protection(&dev->kobj, &attr->attr);
+       if (!kn) {
+               rc = -ENODEV;
+               goto out_autopm;
+       }
        usb_lock_device(hdev);
        if (hub->disconnected) {
                rc = -ENODEV;
@@ -119,9 +145,13 @@ static ssize_t disable_store(struct device *dev, struct device_attribute *attr,
        if (!rc)
                rc = count;
 
-out_hdev_lock:
+ out_hdev_lock:
        usb_unlock_device(hdev);
+       sysfs_unbreak_active_protection(kn);
+ out_autopm:
        usb_autopm_put_interface(intf);
+ out_hub_get:
+       hub_put(hub);
 
        return rc;
 }