Skip to content

Commit 5a118fc

Browse files
oneukumgregkh
authored andcommitted
USB: cdc-wdm: Make wdm_flush() interruptible and add wdm_fsync().
commit 37d2a36 upstream. syzbot is reporting hung task at wdm_flush() [1], for there is a circular dependency that wdm_flush() from flip_close() for /dev/cdc-wdm0 forever waits for /dev/raw-gadget to be closed while close() for /dev/raw-gadget cannot be called unless close() for /dev/cdc-wdm0 completes. Tetsuo Handa considered that such circular dependency is an usage error [2] which corresponds to an unresponding broken hardware [3]. But Alan Stern responded that we should be prepared for such hardware [4]. Therefore, this patch changes wdm_flush() to use wait_event_interruptible_timeout() which gives up after 30 seconds, for hardware that remains silent must be ignored. The 30 seconds are coming out of thin air. Changing wait_event() to wait_event_interruptible_timeout() makes error reporting from close() syscall less reliable. To compensate it, this patch also implements wdm_fsync() which does not use timeout. Those who want to be very sure that data has gone out to the device are now advised to call fsync(), with a caveat that fsync() can return -EINVAL when running on older kernels which do not implement wdm_fsync(). This patch also fixes three more problems (listed below) found during exhaustive discussion and testing. Since multiple threads can concurrently call wdm_write()/wdm_flush(), we need to use wake_up_all() whenever clearing WDM_IN_USE in order to make sure that all waiters are woken up. Also, error reporting needs to use fetch-and-clear approach in order not to report same error for multiple times. Since wdm_flush() checks WDM_DISCONNECTING, wdm_write() should as well check WDM_DISCONNECTING. In wdm_flush(), since locks are not held, it is not safe to dereference desc->intf after checking that WDM_DISCONNECTING is not set [5]. Thus, remove dev_err() from wdm_flush(). [1] https://syzkaller.appspot.com/bug?id=e7b761593b23eb50855b9ea31e3be5472b711186 [2] https://lkml.kernel.org/r/[email protected] [3] https://lkml.kernel.org/r/[email protected] [4] https://lkml.kernel.org/r/[email protected] [5] https://lkml.kernel.org/r/[email protected] Reported-by: syzbot <[email protected]> Cc: stable <[email protected]> Co-developed-by: Tetsuo Handa <[email protected]> Signed-off-by: Tetsuo Handa <[email protected]> Signed-off-by: Oliver Neukum <[email protected]> Cc: Alan Stern <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 2e1905c commit 5a118fc

File tree

1 file changed

+55
-17
lines changed

1 file changed

+55
-17
lines changed

drivers/usb/class/cdc-wdm.c

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
5858

5959
#define WDM_MAX 16
6060

61+
/* we cannot wait forever at flush() */
62+
#define WDM_FLUSH_TIMEOUT (30 * HZ)
63+
6164
/* CDC-WMC r1.1 requires wMaxCommand to be "at least 256 decimal (0x100)" */
6265
#define WDM_DEFAULT_BUFSIZE 256
6366

@@ -151,7 +154,7 @@ static void wdm_out_callback(struct urb *urb)
151154
kfree(desc->outbuf);
152155
desc->outbuf = NULL;
153156
clear_bit(WDM_IN_USE, &desc->flags);
154-
wake_up(&desc->wait);
157+
wake_up_all(&desc->wait);
155158
}
156159

157160
static void wdm_in_callback(struct urb *urb)
@@ -393,6 +396,9 @@ static ssize_t wdm_write
393396
if (test_bit(WDM_RESETTING, &desc->flags))
394397
r = -EIO;
395398

399+
if (test_bit(WDM_DISCONNECTING, &desc->flags))
400+
r = -ENODEV;
401+
396402
if (r < 0) {
397403
rv = r;
398404
goto out_free_mem_pm;
@@ -424,6 +430,7 @@ static ssize_t wdm_write
424430
if (rv < 0) {
425431
desc->outbuf = NULL;
426432
clear_bit(WDM_IN_USE, &desc->flags);
433+
wake_up_all(&desc->wait); /* for wdm_wait_for_response() */
427434
dev_err(&desc->intf->dev, "Tx URB error: %d\n", rv);
428435
rv = usb_translate_errors(rv);
429436
goto out_free_mem_pm;
@@ -583,28 +590,58 @@ static ssize_t wdm_read
583590
return rv;
584591
}
585592

586-
static int wdm_flush(struct file *file, fl_owner_t id)
593+
static int wdm_wait_for_response(struct file *file, long timeout)
587594
{
588595
struct wdm_device *desc = file->private_data;
596+
long rv; /* Use long here because (int) MAX_SCHEDULE_TIMEOUT < 0. */
597+
598+
/*
599+
* Needs both flags. We cannot do with one because resetting it would
600+
* cause a race with write() yet we need to signal a disconnect.
601+
*/
602+
rv = wait_event_interruptible_timeout(desc->wait,
603+
!test_bit(WDM_IN_USE, &desc->flags) ||
604+
test_bit(WDM_DISCONNECTING, &desc->flags),
605+
timeout);
589606

590-
wait_event(desc->wait,
591-
/*
592-
* needs both flags. We cannot do with one
593-
* because resetting it would cause a race
594-
* with write() yet we need to signal
595-
* a disconnect
596-
*/
597-
!test_bit(WDM_IN_USE, &desc->flags) ||
598-
test_bit(WDM_DISCONNECTING, &desc->flags));
599-
600-
/* cannot dereference desc->intf if WDM_DISCONNECTING */
607+
/*
608+
* To report the correct error. This is best effort.
609+
* We are inevitably racing with the hardware.
610+
*/
601611
if (test_bit(WDM_DISCONNECTING, &desc->flags))
602612
return -ENODEV;
603-
if (desc->werr < 0)
604-
dev_err(&desc->intf->dev, "Error in flush path: %d\n",
605-
desc->werr);
613+
if (!rv)
614+
return -EIO;
615+
if (rv < 0)
616+
return -EINTR;
617+
618+
spin_lock_irq(&desc->iuspin);
619+
rv = desc->werr;
620+
desc->werr = 0;
621+
spin_unlock_irq(&desc->iuspin);
622+
623+
return usb_translate_errors(rv);
624+
625+
}
626+
627+
/*
628+
* You need to send a signal when you react to malicious or defective hardware.
629+
* Also, don't abort when fsync() returned -EINVAL, for older kernels which do
630+
* not implement wdm_flush() will return -EINVAL.
631+
*/
632+
static int wdm_fsync(struct file *file, loff_t start, loff_t end, int datasync)
633+
{
634+
return wdm_wait_for_response(file, MAX_SCHEDULE_TIMEOUT);
635+
}
606636

607-
return usb_translate_errors(desc->werr);
637+
/*
638+
* Same with wdm_fsync(), except it uses finite timeout in order to react to
639+
* malicious or defective hardware which ceased communication after close() was
640+
* implicitly called due to process termination.
641+
*/
642+
static int wdm_flush(struct file *file, fl_owner_t id)
643+
{
644+
return wdm_wait_for_response(file, WDM_FLUSH_TIMEOUT);
608645
}
609646

610647
static __poll_t wdm_poll(struct file *file, struct poll_table_struct *wait)
@@ -729,6 +766,7 @@ static const struct file_operations wdm_fops = {
729766
.owner = THIS_MODULE,
730767
.read = wdm_read,
731768
.write = wdm_write,
769+
.fsync = wdm_fsync,
732770
.open = wdm_open,
733771
.flush = wdm_flush,
734772
.release = wdm_release,

0 commit comments

Comments
 (0)