mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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®