[BUG] usb: gadget: u_serial: ttyGS open races with configfs function removal

From: Cen Zhang

Date: Mon Jun 22 2026 - 22:43:36 EST


Hi,

I hit a KASAN use-after-free in the USB gadget serial function when a
ttyGS device is opened while the corresponding configfs gser function
instance is being removed.

The race is between a userspace open of /dev/ttyGS0 and configfs removal
of functions/gser.usb0. gserial_free_line() can free struct gs_port
while the tty device/cdev path can still route a first open to the
embedded tty_port.

A simplified ordering is:

configfs removal racing opener
rmdir functions/gser.usb0 open("/dev/ttyGS0")
gser_free_inst()
gserial_free_line()
gserial_free_port()
kfree(struct gs_port) tty_open()
tty_init_dev()
write through freed tty_port

Observed report:

BUG: KASAN: slab-use-after-free in tty_init_dev.part.0+0xd8/0x2a0

The buggy address belongs to the object at ffff888101011000 which
belongs to the cache kmalloc-2k of size 2048.
The buggy address is located 296 bytes inside of freed 2048-byte region
[ffff888101011000, ffff888101011800).

Write of size 8

Call Trace:
<TASK>
dump_stack_lvl+0x66/0xa0
print_report+0xce/0x630
? fixup_red_left+0x9/0x30
? tty_init_dev.part.0+0xd8/0x2a0
kasan_report+0xe0/0x110
? tty_init_dev.part.0+0xd8/0x2a0
tty_init_dev.part.0+0xd8/0x2a0
tty_open+0x702/0xa40
? 0xffffffffc0000095
? __pfx_tty_open+0x10/0x10
? __pfx_tty_open+0x10/0x10
? chrdev_open+0x143/0x360
chrdev_open+0x157/0x360
? __pfx_chrdev_open+0x10/0x10
? do_dentry_open+0x610/0x7f0
? __pfx_chrdev_open+0x10/0x10
do_dentry_open+0x233/0x7f0
vfs_open+0x5a/0x1b0
path_openat+0x66d/0x1540
? srso_alias_return_thunk+0x5/0xfbef5
? 0xffffffffc0000095
? __pfx_path_openat+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? do_file_open+0x165/0x2b0
do_file_open+0x186/0x2b0
? __pfx_do_file_open+0x10/0x10
? do_raw_spin_unlock+0x9a/0x100
? srso_alias_return_thunk+0x5/0xfbef5
? _raw_spin_unlock+0x23/0x40
? do_raw_spin_unlock+0x9a/0x100
do_sys_openat2+0xce/0x150
? __pfx_do_sys_openat2+0x10/0x10
? __x64_sys_openat+0x99/0x140
__x64_sys_openat+0xd0/0x140
? __pfx___x64_sys_openat+0x10/0x10
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Allocated by task 475:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0x8f/0xa0
gserial_alloc_line_no_console+0x7e/0x4d0
gser_alloc_inst+0x58/0xa0
try_get_usb_function_instance+0xae/0xf0
usb_get_function_instance+0x12/0x50
function_make+0x155/0x290
configfs_mkdir+0x2aa/0x680
vfs_mkdir+0x150/0x390
filename_mkdirat+0x2ef/0x350
__x64_sys_mkdir+0x47/0x60
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 549:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x43/0x70
kfree+0x2f9/0x530
gserial_free_port+0xe1/0x1f0
gserial_free_line+0xa0/0x150
gser_free_inst+0x25/0x30
usb_put_function_instance+0x4e/0x60
config_item_cleanup+0x8f/0xf0
configfs_rmdir+0x39c/0x4f0
vfs_rmdir+0x18d/0x380
filename_rmdir+0x2d2/0x360
__x64_sys_rmdir+0x34/0x50
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Reproducer, run as root on a KASAN kernel:

modprobe libcomposite || true
modprobe usb_f_serial || true
mount -t configfs none /sys/kernel/config 2>/dev/null || true

G=/sys/kernel/config/usb_gadget/gser-race
F=$G/functions/gser.usb0

rmdir "$F" 2>/dev/null || true
rmdir "$G" 2>/dev/null || true

mkdir "$G"
mkdir "$F"

port=$(cat "$F/port_num")
dev=/dev/ttyGS$port

cat > /tmp/ttygs_open_worker.c <<'EOF'
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char **argv)
{
const char *path = argv[1];
time_t end = time(NULL) + 12;

while (time(NULL) < end) {
int fd = open(path, O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd >= 0)
close(fd);
usleep(500);
}

return 0;
}
EOF

gcc -O2 -o /tmp/ttygs_open_worker /tmp/ttygs_open_worker.c

/tmp/ttygs_open_worker "$dev" &
sleep 1
rmdir "$F"
wait

Thanks,
Cen