Re: [BUG] usb: gadgetfs: KASAN null-ptr-deref and intermittent UAF in ep_aio_cancel()
Alan Stern <[email protected]>
| Newsgroups | org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On Wed, Aug 05, 2026 at 12:17:22PM -0400, Alan Stern wrote:
> But as you have seen, there are still other problems in that driver.
>
> However, I'm not sure it's worth working on them. Greg KH is talking
> about getting rid of almost all the drivers in the gadget/legacy
> directory. I don't know if that will include the gadgetfs driver. If
> it does, trying to fix up the driver will be a waste of time.
Not having heard anything from Greg, I'll assume that the driver is not
in any imminent danger.
Accordingly, below is a new patch implementing the updates mentioned
last time. In particular, the ep_aio_cancel() carries out its dequeue
operation in a separate thread, and ep_aio() drops dev->lock before
calling kiocb_set_cancel_fn() and usb_ep_queue().
It will probably be necessary to flush the workqueue in
gadgetfs_unbind() after calling destroy_ep_files(), to make sure that
any work routines scheduled for aio requests are no longer running. We
can worry about that later.
Alan Stern
Index: usb-devel/drivers/usb/gadget/legacy/inode.c
===================================================================
--- usb-devel.orig/drivers/usb/gadget/legacy/inode.c
+++ usb-devel/drivers/usb/gadget/legacy/inode.c
@@ -236,6 +236,8 @@ static void put_ep (struct ep_data *data
static const char *CHIP;
static DEFINE_MUTEX(sb_mutex); /* Serialize superblock operations */
+static DEFINE_SPINLOCK(aio_lock); /* Protect aio cancellation info */
+
/*----------------------------------------------------------------------*/
/* NOTE: don't use dev_printk calls before binding to the gadget
@@ -433,40 +435,98 @@ static long ep_ioctl(struct file *fd, un
/* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */
+enum aio_req_state {
+ AIO_SUBMITTING,
+ AIO_RUNNING,
+ AIO_COMPLETED,
+ AIO_GIVEN_BACK,
+};
+
+enum aio_cancel_state {
+ AIO_NOT_CANCELLED,
+ AIO_UNLINKING,
+ AIO_UNLINK_DONE,
+};
+
struct kiocb_priv {
struct usb_request *req;
struct ep_data *epdata;
struct kiocb *iocb;
struct mm_struct *mm;
- struct work_struct work;
+ struct work_struct copy_work;
+ struct work_struct unlink_work;
void *buf;
struct iov_iter to;
const void *to_free;
unsigned actual;
+ enum aio_req_state req_state;
+ enum aio_cancel_state cancel_state;
};
-static int ep_aio_cancel(struct kiocb *iocb)
+static void ep_unlink_worker(struct work_struct *work)
{
- struct kiocb_priv *priv = iocb->private;
+ struct kiocb_priv *priv;
struct ep_data *epdata;
- int value;
+ enum aio_req_state req_state;
- local_irq_disable();
+ priv = container_of(work, struct kiocb_priv, unlink_work);
epdata = priv->epdata;
- // spin_lock(&epdata->dev->lock);
- if (likely(epdata && epdata->ep && priv->req))
- value = usb_ep_dequeue (epdata->ep, priv->req);
- else
- value = -EINVAL;
- // spin_unlock(&epdata->dev->lock);
- local_irq_enable();
- return value;
+ usb_ep_dequeue(epdata->ep, priv->req);
+
+ spin_lock_irq(&aio_lock);
+ req_state = priv->req_state;
+ priv->cancel_state = AIO_UNLINK_DONE;
+ spin_unlock_irq(&aio_lock);
+
+ /*
+ * priv->req and epdata are freed after unlinking and completion are
+ * both done.
+ * priv itself is freed after unlinking and giveback are both done.
+ */
+ if (req_state >= AIO_COMPLETED) {
+ usb_ep_free_request(epdata->ep, priv->req);
+ put_ep(epdata);
+ if (req_state == AIO_GIVEN_BACK)
+ kfree(priv);
+ }
+}
+
+static int ep_aio_cancel(struct kiocb *iocb)
+{
+ struct kiocb_priv *priv;
+
+ spin_lock_irq(&aio_lock);
+ priv = iocb->private;
+ if (!priv || priv->cancel_state != AIO_NOT_CANCELLED) {
+ spin_unlock_irq(&aio_lock);
+ return -EINVAL; /* Already completed or cancelled */
+ }
+ if (priv->req_state == AIO_SUBMITTING) {
+ priv->cancel_state = AIO_UNLINK_DONE;
+ spin_unlock_irq(&aio_lock);
+ return 0; /* ep_aio() will call us again if needed */
+ }
+
+ priv->cancel_state = AIO_UNLINKING;
+ spin_unlock_irq(&aio_lock);
+
+ /*
+ * usb_ep_dequeue() is allowed to run synchronously, calling the
+ * request's completion handler before it returns.
+ * We are called with the aio core holding the aio's context lock,
+ * and ep_aio_complete() below may call iocb->kio_complete(), which
+ * tries to acquire the context lock, which would cause deadlock.
+ * For this reason, do the dequeue operation in a work routine.
+ */
+ INIT_WORK(&priv->unlink_work, ep_unlink_worker);
+ schedule_work(&priv->unlink_work);
+ return 0;
}
static void ep_user_copy_worker(struct work_struct *work)
{
- struct kiocb_priv *priv = container_of(work, struct kiocb_priv, work);
+ struct kiocb_priv *priv = container_of(work, struct kiocb_priv, copy_work);
struct mm_struct *mm = priv->mm;
struct kiocb *iocb = priv->iocb;
size_t ret;
@@ -482,7 +542,12 @@ static void ep_user_copy_worker(struct w
kfree(priv->buf);
kfree(priv->to_free);
- kfree(priv);
+
+ spin_lock_irq(&aio_lock);
+ priv->req_state = AIO_GIVEN_BACK;
+ if (priv->cancel_state != AIO_UNLINKING)
+ kfree(priv);
+ spin_unlock_irq(&aio_lock);
}
static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req)
@@ -490,11 +555,13 @@ static void ep_aio_complete(struct usb_e
struct kiocb *iocb = req->context;
struct kiocb_priv *priv = iocb->private;
struct ep_data *epdata = priv->epdata;
+ enum aio_req_state new_req_state;
+ enum aio_cancel_state cancel_state;
- /* lock against disconnect (and ideally, cancel) */
- spin_lock(&epdata->dev->lock);
- priv->req = NULL;
- priv->epdata = NULL;
+ /* Prevent future cancellation */
+ spin_lock(&aio_lock);
+ iocb->private = NULL;
+ spin_unlock(&aio_lock);
/* if this was a write or a read returning no data then we
* don't need to copy anything to userspace, so we can
@@ -503,10 +570,9 @@ static void ep_aio_complete(struct usb_e
if (priv->to_free == NULL || unlikely(req->actual == 0)) {
kfree(req->buf);
kfree(priv->to_free);
- kfree(priv);
- iocb->private = NULL;
iocb->ki_complete(iocb,
req->actual ? req->actual : (long)req->status);
+ new_req_state = AIO_GIVEN_BACK;
} else {
/* ep_copy_to_user() won't report both; we hide some faults */
if (unlikely(0 != req->status))
@@ -515,13 +581,24 @@ static void ep_aio_complete(struct usb_e
priv->buf = req->buf;
priv->actual = req->actual;
- INIT_WORK(&priv->work, ep_user_copy_worker);
- schedule_work(&priv->work);
+ new_req_state = AIO_COMPLETED;
}
- usb_ep_free_request(ep, req);
- spin_unlock(&epdata->dev->lock);
- put_ep(epdata);
+ spin_lock(&aio_lock);
+ priv->req_state = new_req_state;
+ cancel_state = priv->cancel_state;
+ spin_unlock(&aio_lock);
+
+ if (new_req_state == AIO_COMPLETED) {
+ INIT_WORK(&priv->copy_work, ep_user_copy_worker);
+ schedule_work(&priv->copy_work);
+ }
+ if (cancel_state != AIO_UNLINKING) {
+ usb_ep_free_request(ep, req);
+ put_ep(epdata);
+ if (new_req_state == AIO_GIVEN_BACK)
+ kfree(priv);
+ }
}
static ssize_t ep_aio(struct kiocb *iocb,
@@ -532,11 +609,12 @@ static ssize_t ep_aio(struct kiocb *iocb
{
struct usb_request *req;
ssize_t value;
+ struct usb_ep *ep;
+ bool need_unlink = false;
iocb->private = priv;
priv->iocb = iocb;
- kiocb_set_cancel_fn(iocb, ep_aio_cancel);
get_ep(epdata);
priv->epdata = epdata;
priv->actual = 0;
@@ -547,10 +625,11 @@ static ssize_t ep_aio(struct kiocb *iocb
*/
spin_lock_irq(&epdata->dev->lock);
value = -ENODEV;
- if (unlikely(epdata->ep == NULL))
+ ep = epdata->ep;
+ if (unlikely(ep == NULL))
goto fail;
- req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC);
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC);
value = -ENOMEM;
if (unlikely(!req))
goto fail;
@@ -560,12 +639,45 @@ static ssize_t ep_aio(struct kiocb *iocb
req->length = len;
req->complete = ep_aio_complete;
req->context = iocb;
- value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC);
+
+ priv->req_state = AIO_SUBMITTING;
+ priv->cancel_state = AIO_NOT_CANCELLED;
+
+ /* Not allowed to manipulate the aio context while holding dev->lock */
+ ++epdata->dev->udc_usage;
+ spin_unlock_irq(&epdata->dev->lock);
+
+ kiocb_set_cancel_fn(iocb, ep_aio_cancel);
+ value = usb_ep_queue(ep, req, GFP_KERNEL);
+
+ spin_lock_irq(&epdata->dev->lock);
+ --epdata->dev->udc_usage;
+
if (unlikely(0 != value)) {
- usb_ep_free_request(epdata->ep, req);
+ spin_lock(&aio_lock);
+ iocb->private = NULL;
+ spin_unlock(&aio_lock);
+
+ usb_ep_free_request(ep, req);
goto fail;
}
spin_unlock_irq(&epdata->dev->lock);
+
+ spin_lock_irq(&aio_lock);
+ if (iocb->private != NULL) {
+ priv->req_state = AIO_RUNNING;
+
+ /* Cancelled before or just after submission? */
+ if (priv->cancel_state == AIO_UNLINK_DONE) {
+ priv->cancel_state = AIO_NOT_CANCELLED;
+ need_unlink = true;
+ }
+ } /* Otherwise already completed */
+ spin_unlock_irq(&aio_lock);
+
+ if (need_unlink) /* Redo cancel that was too early */
+ ep_aio_cancel(iocb);
+
return -EIOCBQUEUED;
fail: