Re: [PATCH] usb: gadget: f_mass_storage: reject relative paths to fix sb_writers deadlock
"Lei, Xue" <[email protected]> Thu, 30 Jul 2026 17:27:32 +0800
| Newsgroups | org.kernel.vger.linux-usb,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
That's good questions.
> What happens if the user gives an absolute path that just happens to
be below the configfs mount point?
For example: echo "/sys/kernel/config/usb_gadget/g1/idVendor" > lun.0/file
This path starts with /, so it passes the buf[0] != '/' check introduced
by the patch.
The path then enters fsg_lun_open() →
filp_open("/sys/kernel/config/...", O_RDWR). During path resolution, the
VFS traverses into the configfs mount, and
open_last_lookups() calls mnt_want_write() on the configfs vfsmount
because O_RDWR is set. This triggers sb_start_write() on the same
configfs superblock
whose sb_writers is already held by the outer vfs_write() →
file_start_write().
This is the same deadlock scenario
> What happens if the user gives a relative path of the form "../filename"?
For example: echo "../some_file" > lun.0/file
The first character of "../some_file" is ., which is not /. The patch
rejects it immediately with -EINVAL before fsg_lun_open() is ever called.
This case is fully handled by the patch. No deadlock, no file open
attempt — clean rejection.
How about the patch
0001-usb-gadget-f_mass_storage-defer-filp_open-to-workque.patch?
The deadlock fundamentally occurs because filp_open() executes in the
same task context that holds sb_writers on the configfs superblock. Any
path-based
validation approach (rejecting relative paths, checking the target
superblock after kern_path(), etc.) is inherently a heuristic — it tries
to predict
which paths would lead to the recursive lock acquisition and reject them
upfront.
And this patch eliminates the problem at its root: by moving
fsg_lun_open() to a worker thread, filp_open() runs in a context that
simply does not
hold sb_writers on any superblock. The recursive acquisition becomes
impossible regardless of what path the user provides — relative,
absolute, pointing to
configfs, or anywhere else. There is no need for path validation
heuristics or filesystem-type checks.
The trade-off is one additional context switch per file attribute write,
which is negligible given that this is a configuration-time operation
(not a data
path), and fsg_lun_open() already performs blocking I/O (opening and
validating a backing file).
BestRegards,
Xue Lei
On 7/29/26 22:12, Alan Stern wrote:
> CAUTION: This email comes from a non Wind River email account!
> Do not click links or open attachments unless you recognize the sender and know the content is safe.
>
> On Wed, Jul 29, 2026 at 06:32:45PM +0800, Xue Lei wrote:
>> A deadlock can occur when writing a relative path to the mass_storage
>> lun file attribute while the process's CWD is on the same configfs
>> mount:
>>
>> write(configfs_fd, "relative_path", ...)
>> -> vfs_write()
>> -> file_start_write() -- acquires sb_writers (configfs sb)
>> -> configfs_write_iter()
>> -> fsg_store_file()
>> -> fsg_lun_open()
>> -> filp_open("relative_path", O_RDWR, ...)
>> -> path_openat()
>> -> open_last_lookups()
>> -> mnt_want_write() -- tries to acquire same sb_writers
>> *** DEADLOCK ***
>>
>> This happens because filp_open() resolves relative paths against the
>> task's CWD. If the CWD is on the same configfs superblock, path_openat()
>> calls mnt_want_write() which calls sb_start_write() on the same
>> superblock, causing a recursive lock acquisition that can deadlock
>> during filesystem freeze.
>>
>> Backing file paths for mass_storage LUNs should always be absolute
>> paths pointing to block devices or regular files. Reject relative
>> paths early in fsg_store_file() to prevent this deadlock.
> What happens if the user gives an absolute path that just happens to be
> below the configfs mount point?
>
> What happens if the user gives a relative path of the form
> "../filename"?
>
> Alan Stern
>
>> Reported-by: [email protected]
>> Closes: https://syzkaller.appspot.com/bug?extid=4c9318af45f0bf2af153
>> Fixes: ef0aa4b92cf1 ("usb: gadget: f_mass_storage: add configfs support")
>> Signed-off-by: Xue Lei <[email protected]>
>> ---
>> drivers/usb/gadget/function/storage_common.c | 6 ++++++
>> 1 file changed, 6 insertions(+)
>>
>> diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c
>> index 75831f2c7abe..d45c7a34df99 100644
>> --- a/drivers/usb/gadget/function/storage_common.c
>> +++ b/drivers/usb/gadget/function/storage_common.c
>> @@ -448,6 +448,12 @@ ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem,
>> if (count > 0 && buf[count-1] == '\n')
>> ((char *) buf)[count-1] = 0; /* Ugh! */
>>
>> + /* Reject relative paths to prevent sb_writers deadlock when
>> + * CWD is on the same filesystem (e.g., configfs).
>> + */
>> + if (count > 0 && buf[0] && buf[0] != '/')
>> + return -EINVAL;
>> +
>> /* Load new medium */
>> down_write(filesem);
>> if (count > 0 && buf[0]) {
>> --
>> 2.49.1
>>
>>
0001-usb-gadget-f_mass_storage-defer-filp_open-to-workque.patch
(text/x-patch, 4.8 KB)
From 112a252cba7e2789ae9aab0d45d5c4722f300b95 Mon Sep 17 00:00:00 2001 From: Xue Lei <[email protected]> Date: Thu, 30 Jul 2026 16:45:36 +0800 Subject: [PATCH] usb: gadget: f_mass_storage: defer filp_open to workqueue to prevent sb_writers deadlock When the mass_storage function is configured via configfs, writing a backing file path to the "file" attribute invokes fsg_lun_open() -> filp_open() within the context of vfs_write() on the configfs attribute file. vfs_write() holds sb_writers on the configfs superblock via file_start_write(). If the path given to filp_open() resolves on the same configfs superblock (either via a relative path when CWD is on configfs, or an absolute path pointing into configfs), mnt_want_write() inside path_openat() attempts to recursively acquire the same sb_writers, creating a deadlock opportunity: CPU0 (write path) CPU1 (freeze path) ---------------- ------------------ sb_start_write() [ok] freeze_super() percpu_down_write() [blocks] sb_start_write() [blocks] --> mutual deadlock The key insight is that the deadlock occurs because filp_open() runs in the same task context that holds sb_writers. If we move the filp_open() call to a different context that does NOT hold sb_writers, the deadlock is impossible regardless of what path is provided. Fix this by deferring the fsg_lun_open() call to a system workqueue via schedule_work() + wait_for_completion(). The worker thread does not hold any sb_writers lock, so filp_open() can safely acquire sb_writers on any superblock without causing recursion. The original caller waits synchronously for the result, preserving the existing synchronous store() semantics expected by configfs. This approach is comprehensive: it handles relative paths, absolute paths into configfs, and any future scenarios where the filesystem topology might create similar sb_writers conflicts -- without needing path validation heuristics. Reported-by: [email protected] Closes: https://syzkaller.appspot.com/bug?extid=4c9318af45f0bf2af153 Fixes: ef0aa4b92cf1 ("usb: gadget: f_mass_storage: add configfs support") Signed-off-by: Xue Lei <[email protected]> --- drivers/usb/gadget/function/storage_common.c | 49 +++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c index 75831f2c7abe..c33591326086 100644 --- a/drivers/usb/gadget/function/storage_common.c +++ b/drivers/usb/gadget/function/storage_common.c @@ -21,9 +21,11 @@ #include <linux/module.h> #include <linux/blkdev.h> +#include <linux/completion.h> #include <linux/file.h> #include <linux/fs.h> #include <linux/kstrtox.h> +#include <linux/workqueue.h> #include <linux/usb/composite.h> #include "storage_common.h" @@ -176,6 +178,49 @@ void fsg_lun_close(struct fsg_lun *curlun) } EXPORT_SYMBOL_GPL(fsg_lun_close); +/* + * When fsg_lun_open() is called from a configfs store handler, the calling + * task holds sb_writers on the configfs superblock (via vfs_write() -> + * file_start_write()). If filp_open() inside fsg_lun_open() resolves the + * path to the same superblock, mnt_want_write() in path_openat() attempts + * to re-acquire sb_writers, which deadlocks against concurrent freeze_super(). + * + * Deferring fsg_lun_open() to a workqueue worker ensures filp_open() runs in + * a context that does not hold any sb_writers, eliminating the deadlock + * regardless of the path provided. + */ +struct fsg_lun_open_work { + struct work_struct work; + struct completion done; + struct fsg_lun *curlun; + const char *filename; + int result; +}; + +static void fsg_lun_open_work_fn(struct work_struct *work) +{ + struct fsg_lun_open_work *ow = + container_of(work, struct fsg_lun_open_work, work); + + ow->result = fsg_lun_open(ow->curlun, ow->filename); + complete(&ow->done); +} + +static int fsg_lun_open_deferred(struct fsg_lun *curlun, const char *filename) +{ + struct fsg_lun_open_work ow; + + INIT_WORK_ONSTACK(&ow.work, fsg_lun_open_work_fn); + init_completion(&ow.done); + ow.curlun = curlun; + ow.filename = filename; + ow.result = -ETIMEDOUT; + schedule_work(&ow.work); + wait_for_completion(&ow.done); + destroy_work_on_stack(&ow.work); + return ow.result; +} + int fsg_lun_open(struct fsg_lun *curlun, const char *filename) { int ro; @@ -451,8 +496,8 @@ ssize_t fsg_store_file(struct fsg_lun *curlun, struct rw_semaphore *filesem, /* Load new medium */ down_write(filesem); if (count > 0 && buf[0]) { - /* fsg_lun_open() will close existing file if any. */ - rc = fsg_lun_open(curlun, buf); + /* Defer to workqueue to avoid sb_writers deadlock. */ + rc = fsg_lun_open_deferred(curlun, buf); if (rc == 0) curlun->unit_attention_data = SS_NOT_READY_TO_READY_TRANSITION; -- 2.49.1