[PATCH RFC] usb: gadget: f_uac: fix multiple bugs in configfs attributes
"syzbot" <[email protected]> Wed, 29 Jul 2026 09:09:54 +0000 (UTC)
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
This patch addresses several issues in the configfs attribute store
functions for the USB Audio Class (UAC) gadget functions (f_uac1,
f_uac1_legacy, and f_uac2):
1. Invalid free and memory leak in UAC1_RATE_ATTRIBUTE and
UAC2_RATE_ATTRIBUTE:
The store functions generated by these macros duplicate the input string
using kstrdup(). The pointer is then passed to strsep(), which modifies it.
If parsing fails, the modified (interior) pointer is passed to kfree(),
causing a KASAN invalid-free. If parsing succeeds, strsep() sets the
pointer to NULL, and kfree(NULL) is a no-op, causing a memory leak. Fix
this by keeping a copy of the original pointer to pass to kfree().
2. Heap buffer overflow in UAC1_RATE_ATTRIBUTE and UAC2_RATE_ATTRIBUTE:
There was no bounds checking on the number of sampling rates parsed. If a
user provided a string with more than UAC_MAX_RATES (10) comma-separated
numbers, the array assignment would write out of bounds. Add a check to
prevent this and return -EINVAL.
3. Incorrect return value and missing newline stripping in
UAC1_ATTRIBUTE_STRING (f_uac1.c):
The store function returned the result of scnprintf() instead of the
original len. This causes userspace writes to assume a partial write
occurred. Additionally, it did not strip trailing newlines. Apply the same
fix as previously done for f_uac2.c.
4. Inverted null check in UAC1_STR_ATTRIBUTE (f_uac1_legacy.c):
The store function had an inverted null check: if (tmp) { ret = -ENOMEM;
goto end; }. This meant it would abort with an out-of-memory error if
allocation succeeded, and proceed to dereference a null pointer if
allocation failed. Fix the check to if (!tmp) and add trailing newline
stripping.
The KASAN invalid-free report for the first issue:
BUG: KASAN: invalid-free in f_uac2_opts_c_srate_store+0x225/0x2b0
drivers/usb/gadget/function/f_uac2.c:2091
Free of addr ffff888113e32b4c by task syz.0.17/6122
CPU: 0 UID: 0 PID: 6122 Comm: syz.0.17 Not tainted syzkaller #1
PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS
1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report_invalid_free+0xea/0x110 mm/kasan/report.c:557
check_slab_allocation+0xd7/0xf0 mm/kasan/common.c:-1
kasan_slab_pre_free include/linux/kasan.h:199 [inline]
slab_free_hook mm/slub.c:2622 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x173/0x640 mm/slub.c:6692
f_uac2_opts_c_srate_store+0x225/0x2b0
drivers/usb/gadget/function/f_uac2.c:2091
flush_write_buffer fs/configfs/file.c:207 [inline]
configfs_write_iter+0x337/0x430 fs/configfs/file.c:229
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x612/0xba0 fs/read_write.c:687
ksys_write+0x150/0x270 fs/read_write.c:739
Fixes: a7339e4f5788 ("usb: gadget: f_uac2: Support multiple sampling rates")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=a4f65284f1451010b0a8
Link: https://syzkaller.appspot.com/ai_job?id=9ef2273f-9be1-4b2e-9b62-48ad434a2ff9
To: "Greg Kroah-Hartman" <[email protected]>
To: <[email protected]>
To: "Julian Scheel" <[email protected]>
Cc: "Christophe JAILLET" <[email protected]>
Cc: "Taegu Ha" <[email protected]>
Cc: "Kees Cook" <[email protected]>
Cc: <[email protected]>
---
diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c
index 85c502e98..44598db19 100644
--- a/drivers/usb/gadget/function/f_uac1.c
+++ b/drivers/usb/gadget/function/f_uac1.c
@@ -1594,6 +1594,7 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac1_opts *opts = to_f_uac1_opts(item); \
+ char *page_copy = NULL; \
char *split_page = NULL; \
int ret = -EINVAL; \
char *token; \
@@ -1608,18 +1609,28 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
\
i = 0; \
memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
- split_page = kstrdup(page, GFP_KERNEL); \
+ page_copy = kstrdup(page, GFP_KERNEL); \
+ if (!page_copy) { \
+ ret = -ENOMEM; \
+ goto end; \
+ } \
+ split_page = page_copy; \
while ((token = strsep(&split_page, ",")) != NULL) { \
ret = kstrtou32(token, 0, &num); \
if (ret) \
goto end; \
\
+ if (i >= UAC_MAX_RATES) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ \
opts->name##s[i++] = num; \
ret = len; \
}; \
\
end: \
- kfree(split_page); \
+ kfree(page_copy); \
mutex_unlock(&opts->lock); \
return ret; \
} \
@@ -1644,7 +1655,7 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac1_opts *opts = to_f_uac1_opts(item); \
- int ret = 0; \
+ int ret = len; \
\
mutex_lock(&opts->lock); \
if (opts->refcnt) { \
@@ -1652,8 +1663,11 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
goto end; \
} \
\
- ret = scnprintf(opts->name, min(sizeof(opts->name), len), \
- "%s", page); \
+ if (len && page[len - 1] == '\n') \
+ len--; \
+ \
+ scnprintf(opts->name, min(sizeof(opts->name), len + 1), \
+ "%s", page); \
\
end: \
mutex_unlock(&opts->lock); \
diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c
index 5d201a2e3..338c04b39 100644
--- a/drivers/usb/gadget/function/f_uac1_legacy.c
+++ b/drivers/usb/gadget/function/f_uac1_legacy.c
@@ -906,15 +906,20 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \
- int ret = -EBUSY; \
+ int ret = len; \
char *tmp; \
\
mutex_lock(&opts->lock); \
- if (opts->refcnt) \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
goto end; \
+ } \
+ \
+ if (len && page[len - 1] == '\n') \
+ len--; \
\
tmp = kstrndup(page, len, GFP_KERNEL); \
- if (tmp) { \
+ if (!tmp) { \
ret = -ENOMEM; \
goto end; \
} \
@@ -922,7 +927,6 @@ static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \
kfree(opts->name); \
opts->name##_alloc = true; \
opts->name = tmp; \
- ret = len; \
\
end: \
mutex_unlock(&opts->lock); \
diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c
index 897787d08..133f09672 100644
--- a/drivers/usb/gadget/function/f_uac2.c
+++ b/drivers/usb/gadget/function/f_uac2.c
@@ -2012,6 +2012,7 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
const char *page, size_t len) \
{ \
struct f_uac2_opts *opts = to_f_uac2_opts(item); \
+ char *page_copy = NULL; \
char *split_page = NULL; \
int ret = -EINVAL; \
char *token; \
@@ -2026,18 +2027,28 @@ static ssize_t f_uac2_opts_##name##_store(struct config_item *item, \
\
i = 0; \
memset(opts->name##s, 0x00, sizeof(opts->name##s)); \
- split_page = kstrdup(page, GFP_KERNEL); \
+ page_copy = kstrdup(page, GFP_KERNEL); \
+ if (!page_copy) { \
+ ret = -ENOMEM; \
+ goto end; \
+ } \
+ split_page = page_copy; \
while ((token = strsep(&split_page, ",")) != NULL) { \
ret = kstrtou32(token, 0, &num); \
if (ret) \
goto end; \
\
+ if (i >= UAC_MAX_RATES) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ \
opts->name##s[i++] = num; \
ret = len; \
}; \
\
end: \
- kfree(split_page); \
+ kfree(page_copy); \
mutex_unlock(&opts->lock); \
return ret; \
} \
base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].