From: Yu Zhang <yuz08559@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [BUG] usb: gadget: configfs: use-after-free of gadget strings during bind and under a bound gadget
Date: Mon, 19 Oct 2026 13:34:15 +1000 [thread overview]
Message-ID: <20261019033415.73542-1-yuz08559@gmail.com> (raw)
Hi,
The gadget-side string configfs objects (the strings/<langid> directories
and the strings beneath them) are created, modified and destroyed with no
serialization against, and no lifetime coupling to, a bound gadget. This
gives two use-after-frees, both reproduced on an acm gadget over
dummy_hcd with KASAN. Both are root/configfs triggered.
== Background ==
Writing the UDC attribute (gadget_dev_desc_UDC_store()) runs
usb_gadget_register_driver() while holding gi->lock, and
configfs_composite_bind() -> configfs_attach_gadget_strings() then walks
gi->string_list and, per language, language->gadget_strings to build the
usb_string tables (the "the gi->lock is hold by the caller" path).
The configfs callbacks that build and tear those lists down do NOT take
gi->lock:
- gadget_language_make() / gadget_language_attr_release() add/remove
languages on gi->string_list;
- gadget_language_string_make() / gadget_language_string_drop() add/
remove strings on language->gadget_strings and adjust nstrings.
(Contrast function_make()/function_drop(), which take gi->lock around
gi->available_func.)
A store to the gadget's "UDC" attribute and a mkdir/rmdir of a child
strings/<langid> directory are not serialized by the configfs core: they
hold disjoint locks (the store holds the gadget-root fragment rwsem plus
gi->lock; the rmdir holds su_mutex plus the langid's own fragment rwsem
plus the strings/ i_rwsem), so they run concurrently on different CPUs.
== UAF #1: gi->string_list walked during bind vs. concurrent rmdir ==
configfs_attach_gadget_strings() dereferences a struct gadget_language
that a concurrent "rmdir strings/<langid>" frees:
BUG: KASAN: slab-use-after-free in configfs_composite_bind+0x2ba/0xe70
Read of size 4 at addr ffff8880085db900 by task ... (language->nstrings)
configfs_composite_bind+0x2ba/0xe70
gadget_bind_driver+0x170/0x5f0
really_probe+...
gadget_dev_desc_UDC_store+0x179/0x220
configfs_write_iter+0x1a7/0x250
vfs_write+0x5df/0x940
Allocated by task ...:
gadget_language_make+0x93/0x340
configfs_mkdir+...
vfs_mkdir+0x1ed/0x4d0
__x64_sys_mkdir+0x47/0x60
Freed by task ...:
kfree+...
config_item_put+0x89/0xa0
configfs_rmdir+0x42f/0x5b0
__x64_sys_rmdir+0x34/0x50
== UAF #2: a bound gadget references strings that rmdir frees ==
configfs_attach_gadget_strings() stores raw pointers to each
language->manufacturer/product/serialnumber (and each string->string)
into the cdev's string tables; usb_gstrings_attach() does not copy the
string contents. Removing a language (or a string) with rmdir while the
gadget is bound frees those buffers in gadget_language_attr_release() /
gadget_string_release(), and the next host string request dereferences
them:
BUG: KASAN: slab-use-after-free in strlen+0xe/0x40
Read of size 1 at addr ffff8880065f2000 by task swapper/2/0 (GET_DESCRIPTOR)
strlen+0xe/0x40
usb_gadget_get_string+0xbb/0x1b0
composite_setup+0x33fa/0x4690
configfs_composite_setup+0xbb/0xd0
Allocated by task ...:
usb_string_copy+0xed/0x110
gadget_language_product_store+0x22/0x60
Freed by task ...:
gadget_language_attr_release+0x36/0x60
config_item_put+...
configfs_rmdir+0x42f/0x5b0
== Reproduction ==
acm gadget on dummy_hcd (CONFIG_USB_DUMMY_HCD, CONFIG_USB_CONFIGFS,
CONFIG_USB_CONFIGFS_ACM), KASAN:
mkdir g1; echo ... > g1/idVendor; echo ... > g1/idProduct
mkdir g1/strings/0x409
echo ... > g1/strings/0x409/{manufacturer,product,serialnumber}
mkdir g1/functions/acm.0
mkdir g1/configs/c.1
ln -s g1/functions/acm.0 g1/configs/c.1/
# race:
echo <udc> > g1/UDC & # bind: walks the string lists
rmdir g1/strings/0x409 # frees the language
UAF #1 fires during the bind walk; UAF #2 fires on the next host string
descriptor request after the language is freed under the bound gadget.
(I widened the two windows with a debug mdelay to make them
deterministic; without it they are narrow races.)
UAF #1 is a narrow race (bind vs. rmdir); UAF #2 does not even need a
race: removing a language under a bound gadget is enough, the next host
string request then reads freed memory.
next reply other threads:[~2026-10-19 3:34 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-19 3:34 Yu Zhang [this message]
2026-10-19 4:48 ` [BUG] usb: gadget: configfs: use-after-free of gadget strings during bind and under a bound gadget Krishna Kurapati
2026-10-19 6:44 ` Greg Kroah-Hartman
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=20261019033415.73542-1-yuz08559@gmail.com \
--to=yuz08559@gmail.com \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
/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.