All of lore.kernel.org
 help / color / mirror / Atom feed
From: Igor Putko <igorpetindev@gmail.com>
To: Andrey Konovalov <andreyknvl@gmail.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org,
	syzbot+b7534c9b380f81a3b4be@syzkaller.appspotmail.com,
	Igor Putko <igorpetindev@gmail.com>
Subject: [PATCH v2] usb: raw-gadget: fix double-free race during UDC unbind and rebind
Date: Sat, 24 Oct 2026 22:07:58 +0300	[thread overview]
Message-ID: <20261024190758.2526-1-igorpetindev@gmail.com> (raw)
In-Reply-To: <20261024183727.42339-1-igorpetindev@gmail.com>

raw-gadget defers endpoint disabling, request freeing, and descriptor
cleanup to dev_free(). However, dev_free() is only called after
dev->count drops to zero in raw_release(), which happens after
usb_gadget_unregister_driver() has already returned.

Once usb_gadget_unregister_driver() finishes, the UDC can be immediately
rebound by another raw_gadget instance. If that new instance enables an
endpoint, it assigns its newly allocated descriptor to ep->ep->desc on
the shared UDC endpoint. When dev_free() from the old instance runs
concurrently, it calls usb_ep_disable() and frees dev->eps[i].ep->desc,
which now belongs to the new instance. The new instance's usb_ep_enable()
subsequently fails with -ESHUTDOWN, and its error path frees the descriptor
again, causing a KASAN double-free.

Tear down active endpoints and requests synchronously in gadget_unbind()
before the UDC can be bound again. Keep the descriptor pointer inside
struct raw_ep and clear ep->ep->desc on error and disable paths so no
dangling pointers are left on the UDC endpoints.

Reported-by: syzbot+b7534c9b380f81a3b4be@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b7534c9b380f81a3b4be
Fixes: f2c2e717642c ("usb: gadget: add raw-gadget interface")
Signed-off-by: Igor Putko <igorpetindev@gmail.com>
---
v1 -> v2:
- Keep ep0 request handling in dev_free() to avoid any lock recursion.
- Synchronously disable and clean up active endpoints in gadget_unbind()
  to prevent use after UDC unbind.
- Update endpoint cleanup in dev_free() to free raw_ep->desc.
---
---
 drivers/usb/gadget/legacy/raw_gadget.c | 30 ++++++++++++++++++++++----
 1 file changed, 26 insertions(+), 4 deletions(-)

diff --git a/drivers/usb/gadget/legacy/raw_gadget.c b/drivers/usb/gadget/legacy/raw_gadget.c
index 4febf8dac7ca..a6b42679acc7 100644
--- a/drivers/usb/gadget/legacy/raw_gadget.c
+++ b/drivers/usb/gadget/legacy/raw_gadget.c
@@ -141,6 +141,7 @@ struct raw_ep {
 	enum ep_state		state;
 	struct usb_ep		*ep;
 	u8			addr;
+	struct usb_endpoint_descriptor *desc;
 	struct usb_request	*req;
 	bool			urb_queued;
 	bool			disabling;
@@ -223,7 +224,9 @@ static void dev_free(struct kref *kref)
 			continue;
 		usb_ep_disable(dev->eps[i].ep);
 		usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req);
-		kfree(dev->eps[i].ep->desc);
+		kfree(dev->eps[i].desc);
+		dev->eps[i].desc = NULL;
+		dev->eps[i].ep->desc = NULL;
 		dev->eps[i].state = STATE_EP_DISABLED;
 	}
 	kfree(dev);
@@ -328,6 +331,20 @@ static int gadget_bind(struct usb_gadget *gadget,
 static void gadget_unbind(struct usb_gadget *gadget)
 {
 	struct raw_dev *dev = get_gadget_data(gadget);
+	int i;
+
+	if (dev) {
+		for (i = 0; i < dev->eps_num; i++) {
+			if (dev->eps[i].state == STATE_EP_DISABLED)
+				continue;
+			usb_ep_disable(dev->eps[i].ep);
+			usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req);
+			kfree(dev->eps[i].desc);
+			dev->eps[i].desc = NULL;
+			dev->eps[i].ep->desc = NULL;
+			dev->eps[i].state = STATE_EP_DISABLED;
+		}
+	}
 
 	set_gadget_data(gadget, NULL);
 	/* Matches kref_get() in gadget_bind(). */
@@ -886,6 +903,7 @@ static int raw_ioctl_ep_enable(struct raw_dev *dev, unsigned long value)
 		if (ret < 0) {
 			dev_err(&dev->gadget->dev,
 				"fail, usb_ep_enable returned %d\n", ret);
+			ep->ep->desc = NULL;
 			goto out_free;
 		}
 		ep->req = usb_ep_alloc_request(ep->ep, GFP_ATOMIC);
@@ -893,10 +911,12 @@ static int raw_ioctl_ep_enable(struct raw_dev *dev, unsigned long value)
 			dev_err(&dev->gadget->dev,
 				"fail, usb_ep_alloc_request failed\n");
 			usb_ep_disable(ep->ep);
+			ep->ep->desc = NULL;
 			ret = -ENOMEM;
 			goto out_free;
 		}
 		ep->state = STATE_EP_ENABLED;
+		ep->desc = desc;
 		ep->ep->driver_data = ep;
 		ret = i;
 		goto out_unlock;
@@ -962,7 +982,9 @@ static int raw_ioctl_ep_disable(struct raw_dev *dev, unsigned long value)
 
 	spin_lock_irqsave(&dev->lock, flags);
 	usb_ep_free_request(dev->eps[i].ep, dev->eps[i].req);
-	kfree(dev->eps[i].ep->desc);
+	kfree(dev->eps[i].desc);
+	dev->eps[i].desc = NULL;
+	dev->eps[i].ep->desc = NULL;
 	dev->eps[i].state = STATE_EP_DISABLED;
 	dev->eps[i].disabling = false;
 
@@ -1010,7 +1032,7 @@ static int raw_ioctl_ep_set_clear_halt_wedge(struct raw_dev *dev,
 		ret = -EINVAL;
 		goto out_unlock;
 	}
-	if (usb_endpoint_xfer_isoc(dev->eps[i].ep->desc)) {
+	if (usb_endpoint_xfer_isoc(dev->eps[i].desc)) {
 		dev_dbg(&dev->gadget->dev,
 				"fail, can't halt/wedge ISO endpoint\n");
 		ret = -EINVAL;
@@ -1096,7 +1118,7 @@ static int raw_process_ep_io(struct raw_dev *dev, struct usb_raw_ep_io *io,
 		ret = -EBUSY;
 		goto out_unlock;
 	}
-	if (in != usb_endpoint_dir_in(ep->ep->desc)) {
+	if (in != usb_endpoint_dir_in(ep->desc)) {
 		dev_dbg(&dev->gadget->dev, "fail, wrong direction\n");
 		ret = -EINVAL;
 		goto out_unlock;
-- 
2.47.3


  parent reply	other threads:[~2026-10-24 19:08 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24 18:37 [PATCH] usb: raw-gadget: fix double-free race during UDC unbind and rebind Igor Putko
2026-10-24 18:48 ` sashiko-bot
2026-10-24 19:07 ` Igor Putko [this message]
2026-10-24 19:28   ` [PATCH v2] " sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261024190758.2526-1-igorpetindev@gmail.com \
    --to=igorpetindev@gmail.com \
    --cc=andreyknvl@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=syzbot+b7534c9b380f81a3b4be@syzkaller.appspotmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.