Linux USB
 help / color / mirror / Atom feed
* [BUG] general protection fault in gadget_dev_ioctl
@ 2026-08-24 11:35 Jaeyoung Chung
  2026-08-25 10:58 ` [PATCH] USB: gadget: fix NULL pointer dereference in gadget_dev_ioctl() Lovekesh Solanki
  0 siblings, 1 reply; 9+ messages in thread
From: Jaeyoung Chung @ 2026-08-24 11:35 UTC (permalink / raw)
  To: gregkh, linux-usb
  Cc: brauner, jack, kees, linux-kernel, mjguzik, viro, eulgyukim,
	jjy600901

Hello,

We found a "general protection fault in gadget_dev_ioctl" on Linux v7.2.
The issue was found by our own race fuzzer. We have not analyzed the root cause,
so we do not have a proposed fix to offer.

To reproduce the race reliably, we applied the delay patch below to the
kernel and ran the C reproducer as root inside an x86_64 QEMU guest. The
crash log we observed, the delay patch and the reproducer are all included
below.

The following kernel config options are required to reproduce the issue:
    CONFIG_USB_SUPPORT=y
    CONFIG_USB=y
    CONFIG_USB_GADGET=y
    CONFIG_USB_DUMMY_HCD=y
    CONFIG_USB_GADGETFS=y
    CONFIG_KASAN=y

We hope this report is useful. Please let us know if any further
information would help.

Reported-by: Eulgyu Kim <eulgyukim@snu.ac.kr>
Reported-by: Jaeyoung Chung <jjy600901@snu.ac.kr>

Kernel delay patch:
==================================================================
diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c
index d87a8ab51510..b996abcae31a 100644
--- a/drivers/usb/gadget/legacy/inode.c
+++ b/drivers/usb/gadget/legacy/inode.c
@@ -1254,6 +1254,10 @@ static long gadget_dev_ioctl (struct file *fd, unsigned code, unsigned long valu
 	struct usb_gadget	*gadget = dev->gadget;
 	long ret = -ENOTTY;
 
+	if (!gadget && strncmp(current->comm, "syzrepro1", 9) == 0) {
+		mdelay(100);
+	}
+
 	spin_lock_irq(&dev->lock);
 	if (dev->state == STATE_DEV_OPENED ||
 			dev->state == STATE_DEV_UNBOUND) {
@@ -1805,6 +1809,9 @@ dev_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
 	unsigned		total;
 	u32			tag;
 	char			*kbuf;
+	if (strncmp(current->comm, "syzrepro0", 9) == 0) {
+		mdelay(60);
+	}
 
 	spin_lock_irq(&dev->lock);
 	if (dev->state > STATE_DEV_OPENED) {
diff --git a/fs/ioctl.c b/fs/ioctl.c
index 1c152c2b1b67..090d2d03d91e 100644
--- a/fs/ioctl.c
+++ b/fs/ioctl.c
@@ -11,6 +11,7 @@
 #include <linux/compat.h>
 #include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/delay.h>
 #include <linux/security.h>
 #include <linux/export.h>
 #include <linux/uaccess.h>
@@ -48,6 +49,10 @@ static int vfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 	if (!filp->f_op->unlocked_ioctl)
 		goto out;
 
+	if (strncmp(current->comm, "syzrepro", 8) == 0) {
+		mdelay(5);
+	}
+
 	error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
 	if (error == -ENOIOCTLCMD)
 		error = -ENOTTY;
@@ -588,6 +593,10 @@ SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
 	if (fd_empty(f))
 		return -EBADF;
 
+	if (strncmp(current->comm, "syzrepro", 8) == 0) {
+		mdelay(5);
+	}
+
 	error = security_file_ioctl(fd_file(f), cmd, arg);
 	if (error)
 		return error;

==================================================================

C reproducer:
==================================================================
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/* tag(4) + full-speed config descriptor(9) + device descriptor(18) = 31 */
static const unsigned char cfg_blob[31] = {
	/* tag = 0 */
	0x00, 0x00, 0x00, 0x00,
	/* struct usb_config_descriptor */
	0x09,		/* bLength            = USB_DT_CONFIG_SIZE */
	0x02,		/* bDescriptorType    = USB_DT_CONFIG */
	0x09, 0x00,	/* wTotalLength       = 9 */
	0x01,		/* bNumInterfaces     = 1 */
	0x01,		/* bConfigurationValue= 1 (must be != 0) */
	0x00,		/* iConfiguration */
	0x80,		/* bmAttributes: ATT_ONE set, ATT_WAKEUP clear */
	0x01,		/* bMaxPower */
	/* struct usb_device_descriptor */
	0x12,		/* bLength            = USB_DT_DEVICE_SIZE */
	0x01,		/* bDescriptorType    = USB_DT_DEVICE */
	0x00, 0x02,	/* bcdUSB */
	0x00, 0x00, 0x00,
	0x40,		/* bMaxPacketSize0 */
	0x00, 0x00,	/* idVendor */
	0x00, 0x00,	/* idProduct */
	0x00, 0x00,	/* bcdDevice */
	0x00, 0x00, 0x00,
	0x01,		/* bNumConfigurations = 1 */
};

static int ep0fd = -1;
static volatile int t1_armed;
static volatile int go;

static void *thr_ioctl(void *arg)	/* syzrepro1: the victim */
{
	long r;

	(void)arg;
	prctl(PR_SET_NAME, "syzrepro1", 0, 0, 0);
	while (!go)
		sched_yield();

	t1_armed = 1;

	r = ioctl(ep0fd, 0x227c /* SG_GET_PACK_ID, value is irrelevant */, 0UL);
	printf("[T1] ioctl -> %ld (errno %d)\n", r, errno);
	fflush(stdout);
	return NULL;
}

static void *thr_write(void *arg)	/* syzrepro0: triggers gadgetfs_bind */
{
	ssize_t n;

	(void)arg;
	prctl(PR_SET_NAME, "syzrepro0", 0, 0, 0);
	while (!go)
		sched_yield();
	while (!t1_armed)
		sched_yield();
	usleep(2000);

	n = write(ep0fd, cfg_blob, sizeof(cfg_blob));
	printf("[T0] write -> %zd (errno %d)\n", n, errno);
	fflush(stdout);
	return NULL;
}

/* find the ep0 file: gadgetfs names it after the UDC (usually dummy_udc) */
static int find_chip(const char *dir, char *out, size_t outsz)
{
	DIR *d;
	struct dirent *e;
	int found = 0;

	d = opendir(dir);
	if (!d) {
		printf("[!] opendir(%s) failed errno=%d\n", dir, errno);
		return -1;
	}
	while ((e = readdir(d)) != NULL) {
		if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, ".."))
			continue;
		snprintf(out, outsz, "%s", e->d_name);
		found = 1;
		if (!strcmp(e->d_name, "dummy_udc"))
			break;	/* preferred */
	}
	closedir(d);
	return found ? 0 : -1;
}

static const char *pick_dir(void)
{
	static const char *cands[] = { "/gadget", "/tmp/gadget", "./gadget",
				       "/root/gadget", NULL };
	int i;

	for (i = 0; cands[i]; i++) {
		if (mkdir(cands[i], 0777) == 0 || errno == EEXIST)
			return cands[i];
		printf("[!] mkdir(%s) failed errno=%d\n", cands[i], errno);
	}
	return NULL;
}

static int one_round(const char *dir, int iter)
{
	char chip[256], path[512];
	pthread_t t0, t1;
	int rc;

	if (mount(NULL, dir, "gadgetfs", 0, NULL) != 0) {
		printf("[%d] mount(gadgetfs) failed errno=%d\n", iter, errno);
		return -1;
	}
	if (find_chip(dir, chip, sizeof(chip)) != 0) {
		printf("[%d] no ep0 file under %s\n", iter, dir);
		umount2(dir, MNT_DETACH);
		return -1;
	}
	snprintf(path, sizeof(path), "%s/%s", dir, chip);

	ep0fd = open(path, O_RDWR);
	if (ep0fd < 0) {
		printf("[%d] open(%s) failed errno=%d\n", iter, path, errno);
		umount2(dir, MNT_DETACH);
		return -1;
	}
	printf("[%d] ep0=%s fd=%d -- racing\n", iter, path, ep0fd);
	fflush(stdout);

	t1_armed = 0;
	go = 0;
	rc = pthread_create(&t1, NULL, thr_ioctl, NULL);
	if (rc) {
		printf("[%d] pthread_create t1 rc=%d\n", iter, rc);
		close(ep0fd);
		umount2(dir, MNT_DETACH);
		return -1;
	}
	rc = pthread_create(&t0, NULL, thr_write, NULL);
	if (rc) {
		printf("[%d] pthread_create t0 rc=%d\n", iter, rc);
		go = 1;
		pthread_join(t1, NULL);
		close(ep0fd);
		umount2(dir, MNT_DETACH);
		return -1;
	}

	go = 1;
	pthread_join(t1, NULL);
	pthread_join(t0, NULL);

	close(ep0fd);
	ep0fd = -1;
	if (umount(dir) != 0) {
		printf("[%d] umount failed errno=%d, detaching\n", iter, errno);
		umount2(dir, MNT_DETACH);
	}
	return 0;
}

int main(int argc, char **argv)
{
	const char *dir;
	int iters = 20;
	int i;

	setvbuf(stdout, NULL, _IONBF, 0);

	/* argv: <marker> <iters> <nthreads> ; nthreads is fixed at 2 here */
	if (argc > 2) {
		int v = atoi(argv[2]);

		if (v > 0)
			iters = v;
	}
	if (argc > 1)
		printf("[*] marker=%s iters=%d (2 racing threads: "
		       "syzrepro0/syzrepro1)\n", argv[1], iters);

	dir = pick_dir();
	if (!dir) {
		printf("[!] no usable mount point\n");
		return 1;
	}
	printf("[*] mount point %s\n", dir);

	/* in case a previous run left it mounted */
	umount2(dir, MNT_DETACH);

	for (i = 0; i < iters; i++) {
		if (one_round(dir, i) != 0)
			usleep(100000);
	}
	printf("[*] done\n");
	return 0;
}
==================================================================

Crash log:
==================================================================
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000005: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f]
CPU: 3 UID: 0 PID: 401 Comm: syzrepro1 Not tainted 7.2.0-dirty #2 PREEMPT 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
RIP: 0010:gadget_dev_ioctl+0xf0/0x240 drivers/usb/gadget/legacy/inode.c:1265
Code: 41 8b 06 49 c7 c6 e7 ff ff ff 83 c8 04 83 f8 05 0f 84 e7 00 00 00 89 6c 24 0c 4c 89 6c 24 10 49 8d 6f 28 49 89 ed 49 c1 ed 03 <43> 80 7c 25 00 00 74 08 48 89 ef e8 40 3c 79 fd 4c 8b 65 00 49 83
RSP: 0018:ffff88810a27fe58 EFLAGS: 00010006
RAX: 0000000000000006 RBX: ffff88810179e800 RCX: 0000000000000001
RDX: 0000000000000001 RSI: 0000000000000004 RDI: ffff88810a27fe3c
RBP: 0000000000000028 R08: ffff88810a27fe3f R09: 1ffff1102144ffc7
R10: dffffc0000000000 R11: ffffed102144ffc8 R12: dffffc0000000000
R13: 0000000000000005 R14: ffffffffffffffe7 R15: 0000000000000000
FS:  000077fc4fcbd6c0(0000) GS:ffff88816488a000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000077fc4fdaf010 CR3: 000000010c042000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 vfs_ioctl fs/ioctl.c:56 [inline]
 __do_sys_ioctl fs/ioctl.c:606 [inline]
 __se_sys_ioctl+0x173/0x1c0 fs/ioctl.c:588
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf7/0x370 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x77fc4fdbed6b
Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
RSP: 002b:000077fc4fcbce70 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 000077fc4fcbd6c0 RCX: 000077fc4fdbed6b
RDX: 0000000000000000 RSI: 000000000000227c RDI: 0000000000000003
RBP: 0000000000000000 R08: 0000000000000000 R09: 00007ffcdf85dd27
R10: 0000000000000000 R11: 0000000000000246 R12: ffffffffffffff80
R13: 0000000000000016 R14: 00007ffcdf85dc30 R15: 000077fc4f4bd000
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:gadget_dev_ioctl+0xf0/0x240 drivers/usb/gadget/legacy/inode.c:1265
Code: 41 8b 06 49 c7 c6 e7 ff ff ff 83 c8 04 83 f8 05 0f 84 e7 00 00 00 89 6c 24 0c 4c 89 6c 24 10 49 8d 6f 28 49 89 ed 49 c1 ed 03 <43> 80 7c 25 00 00 74 08 48 89 ef e8 40 3c 79 fd 4c 8b 65 00 49 83
RSP: 0018:ffff88810a27fe58 EFLAGS: 00010006
RAX: 0000000000000006 RBX: ffff88810179e800 RCX: 0000000000000001
RDX: 0000000000000001 RSI: 0000000000000004 RDI: ffff88810a27fe3c
RBP: 0000000000000028 R08: ffff88810a27fe3f R09: 1ffff1102144ffc7
R10: dffffc0000000000 R11: ffffed102144ffc8 R12: dffffc0000000000
R13: 0000000000000005 R14: ffffffffffffffe7 R15: 0000000000000000
FS:  000077fc4fcbd6c0(0000) GS:ffff88816488a000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000077fc4fdaf010 CR3: 000000010c042000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
   0:	41 8b 06             	mov    (%r14),%eax
   3:	49 c7 c6 e7 ff ff ff 	mov    $0xffffffffffffffe7,%r14
   a:	83 c8 04             	or     $0x4,%eax
   d:	83 f8 05             	cmp    $0x5,%eax
  10:	0f 84 e7 00 00 00    	je     0xfd
  16:	89 6c 24 0c          	mov    %ebp,0xc(%rsp)
  1a:	4c 89 6c 24 10       	mov    %r13,0x10(%rsp)
  1f:	49 8d 6f 28          	lea    0x28(%r15),%rbp
  23:	49 89 ed             	mov    %rbp,%r13
  26:	49 c1 ed 03          	shr    $0x3,%r13
* 2a:	43 80 7c 25 00 00    	cmpb   $0x0,0x0(%r13,%r12,1) <-- trapping instruction
  30:	74 08                	je     0x3a
  32:	48 89 ef             	mov    %rbp,%rdi
  35:	e8 40 3c 79 fd       	callq  0xfd793c7a
  3a:	4c 8b 65 00          	mov    0x0(%rbp),%r12
  3e:	49                   	rex.WB
  3f:	83                   	.byte 0x83
==================================================================



^ permalink raw reply related	[flat|nested] 9+ messages in thread
[parent not found: <20260824160022.2378192-1-jjy600901@snu.ac.kr>]

end of thread, other threads:[~2026-08-25 17:16 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-24 11:35 [BUG] general protection fault in gadget_dev_ioctl Jaeyoung Chung
2026-08-25 10:58 ` [PATCH] USB: gadget: fix NULL pointer dereference in gadget_dev_ioctl() Lovekesh Solanki
2026-08-25 11:07   ` Lovekesh Solanki
2026-08-25 13:14   ` Alan Stern
2026-08-25 14:29     ` Lovekesh Solanki
2026-08-25 15:40       ` Alan Stern
2026-08-25 17:16         ` Lovekesh Solanki
     [not found] <20260824160022.2378192-1-jjy600901@snu.ac.kr>
2026-08-25 10:46 ` Lovekesh Solanki
2026-08-25 11:15   ` Lovekesh Solanki

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox