* [PATCH 0/2] USB: gadget: fix mm lifetime use-after-free in async read paths
@ 2026-05-31 21:04 Gabriel Prostitis via B4 Relay
2026-05-31 21:04 ` [PATCH 1/2] USB: gadget: ffs: fix mm lifetime handling Gabriel Prostitis via B4 Relay
2026-05-31 21:04 ` [PATCH 2/2] USB: gadget: inode: " Gabriel Prostitis via B4 Relay
0 siblings, 2 replies; 4+ messages in thread
From: Gabriel Prostitis via B4 Relay @ 2026-05-31 21:04 UTC (permalink / raw)
To: Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel, Gabriel Prostitis
There is a use-after-free in the USB gadget FunctionFS and legacy
GadgetFS asynchronous read paths. Both implementations store
current->mm in per-request state without taking a reference, and
later use it in a workqueue context via kthread_use_mm().
If the submitting task exits before the USB request completes,
the stored mm_struct may be freed while a pending request still
references it, leading to a use-after-free.
The issue affects:
- FunctionFS: drivers/usb/gadget/function/f_fs.c
- GadgetFS legacy: drivers/usb/gadget/legacy/inode.c
Fix this by taking a reference to mm_struct with mmgrab() when
queueing the request and releasing it with mmdrop() after
completion. Before using the saved mm_struct, acquire a temporary
reference with mmget_not_zero() to ensure it is still alive.
The issue can be triggered by submitting asynchronous reads on
OUT endpoints (e.g. via io_uring for FunctionFS) and exiting the
submitting task before completion. This may result in memory
corruption in the address space of another process if the freed
mm_struct is reclaimed during the race window.
KASAN reports confirm use-after-free in the workqueue completion
path when accessing the stale mm_struct.
Best regards,
Gabriel
Signed-off-by: Gabriel Prostitis <prostitisgabriel@gmail.com>
---
Gabriel Prostitis (2):
USB: gadget: ffs: fix mm lifetime handling
USB: gadget: inode: fix mm lifetime handling
drivers/usb/gadget/function/f_fs.c | 27 ++++++++++++++++++++-------
drivers/usb/gadget/legacy/inode.c | 17 +++++++++++++----
2 files changed, 33 insertions(+), 11 deletions(-)
---
base-commit: 22d91cef94b5b86cff0d68ebfce7741740672704
change-id: 20260531-mm-uaf-fix-91d9dacac692
Best regards,
--
Gabriel Prostitis <prostitisgabriel@gmail.com>
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 1/2] USB: gadget: ffs: fix mm lifetime handling 2026-05-31 21:04 [PATCH 0/2] USB: gadget: fix mm lifetime use-after-free in async read paths Gabriel Prostitis via B4 Relay @ 2026-05-31 21:04 ` Gabriel Prostitis via B4 Relay 2026-05-31 21:04 ` [PATCH 2/2] USB: gadget: inode: " Gabriel Prostitis via B4 Relay 1 sibling, 0 replies; 4+ messages in thread From: Gabriel Prostitis via B4 Relay @ 2026-05-31 21:04 UTC (permalink / raw) To: Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel, Gabriel Prostitis From: Gabriel Prostitis <prostitisgabriel@gmail.com> io_data stores a pointer to the submitting task's mm_struct, but does not currently hold a reference to it while async requests are pending. This can result in a use-after-free if the task exits before completion handling finishes. Take a reference with mmgrab() when queuing the read request and release it with mmdrop() on request completion. Signed-off-by: Gabriel Prostitis <prostitisgabriel@gmail.com> --- drivers/usb/gadget/function/f_fs.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 002c3441bea3..674f2fd5450f 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -864,9 +864,15 @@ static void ffs_user_copy_worker(struct work_struct *work) bool kiocb_has_eventfd = io_data->kiocb->ki_flags & IOCB_EVENTFD; if (io_data->read && ret > 0) { - kthread_use_mm(io_data->mm); - ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); - kthread_unuse_mm(io_data->mm); + if (mmget_not_zero(io_data->mm)) { + kthread_use_mm(io_data->mm); + ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); + kthread_unuse_mm(io_data->mm); + mmput(io_data->mm); + } else { + ret = -EFAULT; + } + mmdrop(io_data->mm); } io_data->kiocb->ki_complete(io_data->kiocb, ret); @@ -1261,16 +1267,20 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from) kiocb->private = p; - if (p->aio) + if (p->aio) { + mmgrab(p->mm); kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } res = ffs_epfile_io(kiocb->ki_filp, p); if (res == -EIOCBQUEUED) return res; - if (p->aio) + if (p->aio) { + mmdrop(p->mm); kfree(p); - else + } else { *from = p->data; + } return res; } @@ -1305,14 +1315,17 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to) kiocb->private = p; - if (p->aio) + if (p->aio) { + mmgrab(p->mm); kiocb_set_cancel_fn(kiocb, ffs_aio_cancel); + } res = ffs_epfile_io(kiocb->ki_filp, p); if (res == -EIOCBQUEUED) return res; if (p->aio) { + mmdrop(p->mm); kfree(p->to_free); kfree(p); } else { -- 2.54.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/2] USB: gadget: inode: fix mm lifetime handling 2026-05-31 21:04 [PATCH 0/2] USB: gadget: fix mm lifetime use-after-free in async read paths Gabriel Prostitis via B4 Relay 2026-05-31 21:04 ` [PATCH 1/2] USB: gadget: ffs: fix mm lifetime handling Gabriel Prostitis via B4 Relay @ 2026-05-31 21:04 ` Gabriel Prostitis via B4 Relay 2026-06-01 1:53 ` Alan Stern 1 sibling, 1 reply; 4+ messages in thread From: Gabriel Prostitis via B4 Relay @ 2026-05-31 21:04 UTC (permalink / raw) To: Greg Kroah-Hartman; +Cc: linux-usb, linux-kernel, Gabriel Prostitis From: Gabriel Prostitis <prostitisgabriel@gmail.com> priv stores a pointer to the submitting task's mm_struct, but does not currently hold a reference to it while async requests are pending. This can result in a use-after-free if the task exits before completion handling finishes. Take a reference with mmgrab() when queuing the read request and release it with mmdrop() on request completion. Signed-off-by: Gabriel Prostitis <prostitisgabriel@gmail.com> --- drivers/usb/gadget/legacy/inode.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index d87a8ab51510..f9b7199afc9e 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) struct kiocb *iocb = priv->iocb; size_t ret; - kthread_use_mm(mm); - ret = copy_to_iter(priv->buf, priv->actual, &priv->to); - kthread_unuse_mm(mm); - if (!ret) + if (mmget_not_zero(mm)) { + kthread_use_mm(mm); + ret = copy_to_iter(priv->buf, priv->actual, &priv->to); + kthread_unuse_mm(mm); + mmput(mm); + if (!ret) + ret = -EFAULT; + } else { ret = -EFAULT; + } + mmdrop(mm); /* completing the iocb can drop the ctx and mm, don't touch mm after */ iocb->ki_complete(iocb, ret); @@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) * complete the aio request immediately. */ if (priv->to_free == NULL || unlikely(req->actual == 0)) { + mmdrop(priv->mm); kfree(req->buf); kfree(priv->to_free); kfree(priv); @@ -540,6 +547,7 @@ static ssize_t ep_aio(struct kiocb *iocb, get_ep(epdata); priv->epdata = epdata; priv->actual = 0; + mmgrab(priv->mm); priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ /* each kiocb is coupled to one usb_request, but we can't @@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, fail: spin_unlock_irq(&epdata->dev->lock); + mmdrop(priv->mm); kfree(priv->to_free); kfree(priv); put_ep(epdata); -- 2.54.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 2/2] USB: gadget: inode: fix mm lifetime handling 2026-05-31 21:04 ` [PATCH 2/2] USB: gadget: inode: " Gabriel Prostitis via B4 Relay @ 2026-06-01 1:53 ` Alan Stern 0 siblings, 0 replies; 4+ messages in thread From: Alan Stern @ 2026-06-01 1:53 UTC (permalink / raw) To: prostitisgabriel; +Cc: Greg Kroah-Hartman, linux-usb, linux-kernel On Sun, May 31, 2026 at 11:04:41PM +0200, Gabriel Prostitis via B4 Relay wrote: > From: Gabriel Prostitis <prostitisgabriel@gmail.com> > > priv stores a pointer to the submitting task's mm_struct, > but does not currently hold a reference to it while async > requests are pending. > > This can result in a use-after-free if the task exits before > completion handling finishes. > > Take a reference with mmgrab() when queuing the read request > and release it with mmdrop() on request completion. > > Signed-off-by: Gabriel Prostitis <prostitisgabriel@gmail.com> > --- > drivers/usb/gadget/legacy/inode.c | 17 +++++++++++++---- > 1 file changed, 13 insertions(+), 4 deletions(-) > > diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c > index d87a8ab51510..f9b7199afc9e 100644 > --- a/drivers/usb/gadget/legacy/inode.c > +++ b/drivers/usb/gadget/legacy/inode.c > @@ -471,11 +471,17 @@ static void ep_user_copy_worker(struct work_struct *work) > struct kiocb *iocb = priv->iocb; > size_t ret; > > - kthread_use_mm(mm); > - ret = copy_to_iter(priv->buf, priv->actual, &priv->to); > - kthread_unuse_mm(mm); > - if (!ret) > + if (mmget_not_zero(mm)) { > + kthread_use_mm(mm); > + ret = copy_to_iter(priv->buf, priv->actual, &priv->to); > + kthread_unuse_mm(mm); > + mmput(mm); > + if (!ret) > + ret = -EFAULT; > + } else { > ret = -EFAULT; > + } > + mmdrop(mm); > > /* completing the iocb can drop the ctx and mm, don't touch mm after */ > iocb->ki_complete(iocb, ret); > @@ -501,6 +507,7 @@ static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) > * complete the aio request immediately. > */ > if (priv->to_free == NULL || unlikely(req->actual == 0)) { > + mmdrop(priv->mm); > kfree(req->buf); > kfree(priv->to_free); > kfree(priv); > @@ -540,6 +547,7 @@ static ssize_t ep_aio(struct kiocb *iocb, > get_ep(epdata); > priv->epdata = epdata; > priv->actual = 0; > + mmgrab(priv->mm); This new statement is certainly in the wrong place, because priv->mm isn't assigned until the next line. The two lines should be swapped. After you fix that glaring mistake you can add: Acked-by: Alan Stern <stern@rowland.harvard.edu> Alan Stern > priv->mm = current->mm; /* mm teardown waits for iocbs in exit_aio() */ > > /* each kiocb is coupled to one usb_request, but we can't > @@ -570,6 +578,7 @@ static ssize_t ep_aio(struct kiocb *iocb, > > fail: > spin_unlock_irq(&epdata->dev->lock); > + mmdrop(priv->mm); > kfree(priv->to_free); > kfree(priv); > put_ep(epdata); > > -- > 2.54.0 > > > ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-06-01 1:53 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-05-31 21:04 [PATCH 0/2] USB: gadget: fix mm lifetime use-after-free in async read paths Gabriel Prostitis via B4 Relay 2026-05-31 21:04 ` [PATCH 1/2] USB: gadget: ffs: fix mm lifetime handling Gabriel Prostitis via B4 Relay 2026-05-31 21:04 ` [PATCH 2/2] USB: gadget: inode: " Gabriel Prostitis via B4 Relay 2026-06-01 1:53 ` Alan Stern
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®