Commit 4e9c93af authored by Shuah Khan's avatar Shuah Khan Committed by Greg Kroah-Hartman
Browse files

usbip: add sysfs_lock to synchronize sysfs code paths



Fuzzing uncovered race condition between sysfs code paths in usbip
drivers. Device connect/disconnect code paths initiated through
sysfs interface are prone to races if disconnect happens during
connect and vice versa.

This problem is common to all drivers while it can be reproduced easily
in vhci_hcd. Add a sysfs_lock to usbip_device struct to protect the paths.

Use this in vhci_hcd to protect sysfs paths. For a complete fix, usip_host
and usip-vudc drivers and the event handler will have to use this lock to
protect the paths. These changes will be done in subsequent patches.

Cc: stable@vger.kernel.org
Reported-and-tested-by: default avatar <syzbot+a93fba6d384346a761e3@syzkaller.appspotmail.com>
Signed-off-by: default avatarShuah Khan <skhan@linuxfoundation.org>
Link: https://lore.kernel.org/r/b6568f7beae702bbc236a545d3c020106ca75eac.1616807117.git.skhan@linuxfoundation.org


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 3004fcba
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -263,6 +263,9 @@ struct usbip_device {
	/* lock for status */
	spinlock_t lock;

	/* mutex for synchronizing sysfs store paths */
	struct mutex sysfs_lock;

	int sockfd;
	struct socket *tcp_socket;

+1 −0
Original line number Diff line number Diff line
@@ -1101,6 +1101,7 @@ static void vhci_device_init(struct vhci_device *vdev)
	vdev->ud.side   = USBIP_VHCI;
	vdev->ud.status = VDEV_ST_NULL;
	spin_lock_init(&vdev->ud.lock);
	mutex_init(&vdev->ud.sysfs_lock);

	INIT_LIST_HEAD(&vdev->priv_rx);
	INIT_LIST_HEAD(&vdev->priv_tx);
+25 −5
Original line number Diff line number Diff line
@@ -185,6 +185,8 @@ static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport)

	usbip_dbg_vhci_sysfs("enter\n");

	mutex_lock(&vdev->ud.sysfs_lock);

	/* lock */
	spin_lock_irqsave(&vhci->lock, flags);
	spin_lock(&vdev->ud.lock);
@@ -195,6 +197,7 @@ static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport)
		/* unlock */
		spin_unlock(&vdev->ud.lock);
		spin_unlock_irqrestore(&vhci->lock, flags);
		mutex_unlock(&vdev->ud.sysfs_lock);

		return -EINVAL;
	}
@@ -205,6 +208,8 @@ static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport)

	usbip_event_add(&vdev->ud, VDEV_EVENT_DOWN);

	mutex_unlock(&vdev->ud.sysfs_lock);

	return 0;
}

@@ -349,30 +354,36 @@ static ssize_t attach_store(struct device *dev, struct device_attribute *attr,
	else
		vdev = &vhci->vhci_hcd_hs->vdev[rhport];

	mutex_lock(&vdev->ud.sysfs_lock);

	/* Extract socket from fd. */
	socket = sockfd_lookup(sockfd, &err);
	if (!socket) {
		dev_err(dev, "failed to lookup sock");
		return -EINVAL;
		err = -EINVAL;
		goto unlock_mutex;
	}
	if (socket->type != SOCK_STREAM) {
		dev_err(dev, "Expecting SOCK_STREAM - found %d",
			socket->type);
		sockfd_put(socket);
		return -EINVAL;
		err = -EINVAL;
		goto unlock_mutex;
	}

	/* create threads before locking */
	tcp_rx = kthread_create(vhci_rx_loop, &vdev->ud, "vhci_rx");
	if (IS_ERR(tcp_rx)) {
		sockfd_put(socket);
		return -EINVAL;
		err = -EINVAL;
		goto unlock_mutex;
	}
	tcp_tx = kthread_create(vhci_tx_loop, &vdev->ud, "vhci_tx");
	if (IS_ERR(tcp_tx)) {
		kthread_stop(tcp_rx);
		sockfd_put(socket);
		return -EINVAL;
		err = -EINVAL;
		goto unlock_mutex;
	}

	/* get task structs now */
@@ -397,7 +408,8 @@ static ssize_t attach_store(struct device *dev, struct device_attribute *attr,
		 * Will be retried from userspace
		 * if there's another free port.
		 */
		return -EBUSY;
		err = -EBUSY;
		goto unlock_mutex;
	}

	dev_info(dev, "pdev(%u) rhport(%u) sockfd(%d)\n",
@@ -423,7 +435,15 @@ static ssize_t attach_store(struct device *dev, struct device_attribute *attr,

	rh_port_connect(vdev, speed);

	dev_info(dev, "Device attached\n");

	mutex_unlock(&vdev->ud.sysfs_lock);

	return count;

unlock_mutex:
	mutex_unlock(&vdev->ud.sysfs_lock);
	return err;
}
static DEVICE_ATTR_WO(attach);