From: Slawomir Stepien <sst@poczta.fm>
To: syzbot <syzbot@kernel.org>
Cc: syzkaller-upstream-moderation@googlegroups.com, syzbot@lists.linux.dev
Subject: Re: [PATCH RFC] usb: gadget: f_tcm: defer TPG assignment to fix circular locking
Date: Thu, 15 Oct 2026 09:37:51 +0200 [thread overview]
Message-ID: <atCCz_-iwbVFC5w_@nr200> (raw)
In-Reply-To: <b1646de9-ae67-4626-89f3-76a04e181581@mail.kernel.org>
On paź 06, 2026 00:20, 'syzbot' via syzkaller-upstream-moderation wrote:
> A circular locking dependency exists between tpg_instances_lock, the
> configfs directory inode_lock, and the gadget info lock gi->lock when
> configuring TCM gadget functions through configfs.
>
> WARNING: possible circular locking dependency detected
> ======================================================
> task is trying to acquire lock:
> ffffffff8f24ae58 (tpg_instances_lock){+.+.}-{4:4}, at:
> tcm_alloc+0x21/0x2f0 drivers/usb/gadget/function/f_tcm.c:2734
>
> but task is already holding lock:
> ffff888192990388 (&gi->lock){+.+.}-{4:4}, at:
> config_usb_cfg_link+0x59/0x260 drivers/usb/gadget/configfs.c:439
>
> which lock already depends on the new lock.
>
> the existing dependency chain (in reverse order) is:
>
> -> #2 (&gi->lock){+.+.}-{4:4}:
> config_usb_cfg_link+0x59/0x260 drivers/usb/gadget/configfs.c:439
> configfs_symlink+0x74e/0x11d0 fs/configfs/symlink.c:211
> vfs_symlink+0x18b/0x330 fs/namei.c:5794
>
> -> #1 (&sb->s_type->i_mutex_key#24){+.+.}-{4:4}:
> inode_lock include/linux/fs.h:1024 [inline]
> configfs_depend_item_unlocked+0x153/0x420 fs/configfs/dir.c:1268
> usbg_make_tpg+0x1e0/0x550 drivers/usb/gadget/function/f_tcm.c:1692
> target_fabric_make_tpg+0xa8/0x6d0
> drivers/target/target_core_fabric_configfs.c:947
> configfs_mkdir+0x4f6/0x9e0 fs/configfs/dir.c:1369
>
> -> #0 (tpg_instances_lock){+.+.}-{4:4}:
> tcm_alloc+0x21/0x2f0 drivers/usb/gadget/function/f_tcm.c:2734
> usb_get_function+0x63/0xb0 drivers/usb/gadget/functions.c:61
> config_usb_cfg_link+0x148/0x260 drivers/usb/gadget/configfs.c:469
> configfs_symlink+0x74e/0x11d0 fs/configfs/symlink.c:211
>
> The deadlock arises from two conflicting lock acquisition paths across
> configfs:
>
> 1. When creating a Target Portal Group (TPG) directory in configfs (e.g.
> target/usb_gadget/.../tpgt_1), VFS takes the directory inode_lock. In
> usbg_make_tpg(), tpg_instances_lock is acquired. While holding
> tpg_instances_lock, usbg_make_tpg() calls configfs_depend_item_unlocked()
> to depend on the gadget function item, which acquires the configfs root
> directory inode_lock across subsystems (tpg_instances_lock -> inode_lock).
>
> 2. When linking the TCM function to a gadget configuration (e.g. symlink
> functions/tcm.usb0 configs/c.1/tcm.usb0), VFS acquires the configuration
> directory inode_lock. The callback configfs_symlink() calls
> config_usb_cfg_link(), acquiring gi->lock. While holding gi->lock,
> config_usb_cfg_link() calls usb_get_function(), invoking tcm_alloc(). In
> tcm_alloc(), tpg_instances_lock was acquired to search the tpg_instances
> array and set fu->tpg (inode_lock -> gi->lock -> tpg_instances_lock).
>
> In addition to the deadlock, initializing fu->tpg in tcm_alloc() introduces
> a race condition. If the user symlinks the function into a gadget
> configuration before creating the TPG directory, tpg_instances[i].tpg is
> NULL, causing tcm_alloc() to leave fu->tpg as NULL. When the TPG is later
> created and enabled, tcm_bind() succeeds because opts->can_attach is true,
> but fu->tpg remains NULL. When USB transfers are subsequently processed,
> dereferencing fu->tpg results in a NULL pointer dereference crash.
> Furthermore, removing an enabled TPG via rmdir calls usbg_drop_tpg()
> directly without detaching, leaving opts->can_attach set to true and
> permitting subsequent binds against a destroyed TPG.
>
> Fix these issues by storing the TPG pointer in struct f_tcm_opts, removing
> the tpg_instances_lock acquisition from tcm_alloc(), and deferring fu->tpg
> assignment until tcm_bind(). The function instance is already valid for the
> item's lifetime, making the search in tcm_alloc() redundant; removing it
> breaks the circular lock dependency cycle. In tcm_bind(), fu->tpg is
> assigned under opts->dep_lock after validating that both opts->can_attach
> and opts->tpg are valid. In usbg_make_tpg(), opts->tpg is set under
> opts->dep_lock. In usbg_drop_tpg(), both opts->can_attach is reset to false
> and opts->tpg is cleared under opts->dep_lock, ensuring consistent state
> and preventing NULL pointer dereferences.
>
> Fixes: 4bb8548df632 ("usb: gadget: f_tcm: add configfs support")
> Assisted-by: Gemini:gemini-3.8-flash Gemini:gemini-3.1-pro-preview syzbot
> Reported-by: syzbot+2799146a53c356f8017d@syzkaller.appspotmail.com
> Closes: https://syzkaller.appspot.com/bug?extid=2799146a53c356f8017d
> Link: https://syzkaller.appspot.com/ai_job?id=a5b86f29-4d77-48c4-90c7-2467ea27765f
> To: "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>
> To: <linux-usb@vger.kernel.org>
> To: "Andrzej Pietrasiewicz" <andrzej.p@samsung.com>
> Cc: "Jiasheng Jiang" <jiashengjiangcool@gmail.com>
> Cc: <linux-kernel@vger.kernel.org>
> Cc: "Yun Zhou" <yun.zhou@windriver.com>
> Cc: "Cen Zhang" <zzzccc427@gmail.com>
>
> ---
> diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
> index 9e6d4f399..c7bdedd46 100644
> --- a/drivers/usb/gadget/function/f_tcm.c
> +++ b/drivers/usb/gadget/function/f_tcm.c
> @@ -1720,6 +1720,9 @@ static struct se_portal_group *usbg_make_tpg(struct se_wwn *wwn,
>
> tpg_instances[i].tpg = tpg;
> tpg->fi = tpg_instances[i].func_inst;
> + mutex_lock(&opts->dep_lock);
> + opts->tpg = tpg;
> + mutex_unlock(&opts->dep_lock);
> mutex_unlock(&tpg_instances_lock);
> return &tpg->se_tpg;
>
> @@ -1760,6 +1763,8 @@ static void usbg_drop_tpg(struct se_portal_group *se_tpg)
> opts = container_of(tpg_instances[i].func_inst,
> struct f_tcm_opts, func_inst);
> mutex_lock(&opts->dep_lock);
> + opts->can_attach = false;
> + opts->tpg = NULL;
> if (opts->has_dep)
> module_put(opts->dependent);
> else
> @@ -2300,10 +2305,11 @@ static int tcm_bind(struct usb_configuration *c, struct usb_function *f)
> opts = container_of(f->fi, struct f_tcm_opts, func_inst);
>
> mutex_lock(&opts->dep_lock);
> - if (!opts->can_attach) {
> + if (!opts->can_attach || !opts->tpg) {
> mutex_unlock(&opts->dep_lock);
> return -ENODEV;
> }
> + fu->tpg = opts->tpg;
> mutex_unlock(&opts->dep_lock);
> us = usb_gstrings_attach(c->cdev, tcm_strings,
> ARRAY_SIZE(tcm_us_strings));
> @@ -2729,22 +2735,10 @@ static void tcm_unbind(struct usb_configuration *c, struct usb_function *f)
> static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
> {
> struct f_uas *fu;
> - unsigned i;
> -
> - mutex_lock(&tpg_instances_lock);
> - for (i = 0; i < TPG_INSTANCES; ++i)
> - if (tpg_instances[i].func_inst == fi)
> - break;
> - if (i == TPG_INSTANCES) {
> - mutex_unlock(&tpg_instances_lock);
> - return ERR_PTR(-ENODEV);
> - }
>
> fu = kzalloc_obj(*fu);
> - if (!fu) {
> - mutex_unlock(&tpg_instances_lock);
> + if (!fu)
> return ERR_PTR(-ENOMEM);
> - }
>
> fu->function.name = "Target Function";
> fu->function.bind = tcm_bind;
> @@ -2754,12 +2748,10 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
> fu->function.setup = tcm_setup;
> fu->function.disable = tcm_disable;
> fu->function.free_func = tcm_free;
> - fu->tpg = tpg_instances[i].tpg;
> INIT_WORK(&fu->delayed_set_alt, tcm_delayed_set_alt);
> spin_lock_init(&fu->delayed_set_alt_lock);
>
> hash_init(fu->stream_hash);
> - mutex_unlock(&tpg_instances_lock);
>
> return &fu->function;
> }
> diff --git a/drivers/usb/gadget/function/u_tcm.h b/drivers/usb/gadget/function/u_tcm.h
> index 2cd15d9a1..1376b6ebc 100644
> --- a/drivers/usb/gadget/function/u_tcm.h
> +++ b/drivers/usb/gadget/function/u_tcm.h
> @@ -15,6 +15,8 @@
>
> #include <linux/usb/composite.h>
>
> +struct usbg_tpg;
> +
> /**
> * @dependent: optional dependent module. Meant for legacy gadget.
> * If non-null its refcount will be increased when a tpg is created and
> @@ -23,10 +25,12 @@
> * @ready: true if the dependent module information is set.
> * @can_attach: true a function can be bound to gadget
> * @has_dep: true if there is a dependent module
> + * @tpg: target portal group
This new comment is lacking a bit in content. Explain a bit more what @tpg member holds in this
structure.
> *
> */
> struct f_tcm_opts {
> struct usb_function_instance func_inst;
> + struct usbg_tpg *tpg;
> struct module *dependent;
> struct mutex dep_lock;
> bool ready;
>
>
> base-commit: a90ee4305c4a5df72c11b31dacfdc76e00fcf78a
--
Slawomir Stepien
prev parent reply other threads:[~2026-10-15 7:38 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-06 0:20 [PATCH RFC] usb: gadget: f_tcm: defer TPG assignment to fix circular locking syzbot
2026-10-15 7:37 ` Slawomir Stepien [this message]
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=atCCz_-iwbVFC5w_@nr200 \
--to=sst@poczta.fm \
--cc=syzbot@kernel.org \
--cc=syzbot@lists.linux.dev \
--cc=syzkaller-upstream-moderation@googlegroups.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox