[PATCH v2] usb: gadget: f_loopback: fix descriptor leak on unbind
From: Chaithanya Lagisetty
Date: Tue Sep 15 2026 - 04:58:37 EST
loopback_bind() allocates descriptor copies through
usb_assign_descriptors(), but f_loopback does not release them during
the unbind path. On every bind/unbind cycle of the gadget (for example
by repeatedly writing the UDC attribute through configfs) a new set of
descriptors is allocated while the previous ones are leaked. syzbot
reported this via kmemleak:
BUG: memory leak
unreferenced object 0xffff888016b8f180 (size 64):
comm "repro", pid 5613
backtrace:
__kmalloc_noprof+0x3bf/0x550
usb_copy_descriptors+0x6c/0x160
usb_assign_descriptors+0x48/0x180
loopback_bind+0xff/0x120
usb_add_function+0xca/0x270
configfs_composite_bind+0x667/0x9b0
gadget_bind_driver+0xed/0x390
Move descriptor cleanup to a new loopback_unbind() callback that frees
them with usb_free_all_descriptors(), matching the lifecycle used by
other gadget functions such as f_acm. With descriptors released during
unbind, the usb_free_all_descriptors() call in lb_free_func() becomes
redundant and can be removed.
Tested with CONFIG_DEBUG_KMEMLEAK=y, CONFIG_USB_CONFIGFS_F_LB_SS=y and
CONFIG_USBIP_VUDC=y by running the syzbot reproducer in QEMU with
kmemleak=on. The patched and unpatched kernels were built from the same
tree, config and compiler, so this patch is the only difference between
them. Without it, five reproducer iterations produce three leak reports,
every one of them through loopback_bind(). With it applied, 25
iterations produce none and kmemleak stays silent.
Fixes: 10287baec761 ("usb: gadget: always update HS/SS descriptors and create a copy of them")
Reported-by: syzbot+28cf08dec5895bd562e6@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=28cf08dec5895bd562e6
Tested-by: Chaithanya Lagisetty <nagachaithanya9911@xxxxxxxxx>
Assisted-by: Cursor:claude-opus-5
Signed-off-by: Chaithanya Lagisetty <nagachaithanya9911@xxxxxxxxx>
---
v2:
- Add the Assisted-by: tag that should have been in v1 (Greg KH).
- Describe the kmemleak testing in the commit message (Greg KH).
- No change to the code; the diff is identical to v1.
Link to v1:
https://lore.kernel.org/all/20260808181504.462492-1-nagachaithanya9911@xxxxxxxxx/
drivers/usb/gadget/function/f_loopback.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/drivers/usb/gadget/function/f_loopback.c b/drivers/usb/gadget/function/f_loopback.c
index d2d07fb49e70..40aaf2eb00f2 100644
--- a/drivers/usb/gadget/function/f_loopback.c
+++ b/drivers/usb/gadget/function/f_loopback.c
@@ -216,6 +216,11 @@ static int loopback_bind(struct usb_configuration *c, struct usb_function *f)
return 0;
}
+static void loopback_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ usb_free_all_descriptors(f);
+}
+
static void lb_free_func(struct usb_function *f)
{
struct f_lb_opts *opts;
@@ -226,7 +231,6 @@ static void lb_free_func(struct usb_function *f)
opts->refcnt--;
mutex_unlock(&opts->lock);
- usb_free_all_descriptors(f);
kfree(func_to_loop(f));
}
@@ -442,6 +446,7 @@ static struct usb_function *loopback_alloc(struct usb_function_instance *fi)
loop->function.name = "loopback";
loop->function.bind = loopback_bind;
+ loop->function.unbind = loopback_unbind;
loop->function.set_alt = loopback_set_alt;
loop->function.disable = loopback_disable;
loop->function.strings = loopback_strings;
--
2.43.0