[BUG] usbip: vhci-hcd: status sysfs read races with HCD teardown

From: Cen Zhang

Date: Mon Jun 22 2026 - 23:16:30 EST


Hi,

I hit a KASAN use-after-free in usbip vhci-hcd when reading the vhci
status sysfs file while the vhci platform device is being rebound through
the driver core test-remove path.

VHCI exposes sysfs files under:

/sys/devices/platform/vhci_hcd.0/

The status callback can walk VHCI controller state, including the shared
SuperSpeed HCD. In the teardown path, the shared HCD can be removed and
put before the sysfs group is withdrawn, leaving a window where
status_show() can dereference freed HCD state.

Observed report:

BUG: KASAN: slab-use-after-free in _raw_spin_lock+0x30/0x40
Read of size 1 at addr ffff8880095f4478 by task cat/554

CPU: 0 UID: 0 PID: 554 Comm: cat Not tainted
7.1.0-rc2-00375-g917719c412c4 #3 PREEMPT(lazy)

Call Trace:
<TASK>
dump_stack_lvl+0x66/0xa0
print_report+0xce/0x630
? _raw_spin_lock+0x30/0x40
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x188/0x320
? _raw_spin_lock+0x30/0x40
kasan_report+0xe0/0x110
? _raw_spin_lock+0x30/0x40
? _raw_spin_lock+0x30/0x40
__kasan_check_byte+0x36/0x50
lock_acquire+0x11f/0x300
? status_show+0x2cb/0x3d0
? srso_alias_return_thunk+0x5/0xfbef5
? lock_release+0xc8/0x290
? __asan_memcpy+0x3c/0x60
_raw_spin_lock+0x30/0x40
? status_show+0x320/0x3d0
status_show+0x320/0x3d0
? __pfx_status_show+0x10/0x10
? __pfx_status_show+0x10/0x10
? dev_attr_show+0x24/0x90
dev_attr_show+0x3b/0x90
sysfs_kf_seq_show+0x115/0x1a0
? __pfx_dev_attr_show+0x10/0x10
seq_read_iter+0x29d/0x790
vfs_read+0x406/0x590
? __pfx_vfs_read+0x10/0x10
ksys_read+0xd2/0x170
? __pfx_ksys_read+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? srso_alias_return_thunk+0x5/0xfbef5
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>

Allocated by task 545:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0x8f/0xa0
__kmalloc_noprof+0x28f/0x760
__usb_create_hcd+0x45/0x500
vhci_hcd_probe+0xd9/0x250
platform_probe+0x69/0xe0
really_probe+0x163/0x660
__driver_probe_device+0x106/0x240
device_driver_attach+0x7d/0x110
bind_store+0x95/0xe0
kernfs_fop_write_iter+0x1e0/0x280
vfs_write+0x469/0x810
ksys_write+0xd2/0x170
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 545:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x43/0x70
kfree+0x315/0x540
vhci_hcd_remove+0x66/0xc0
really_probe+0x316/0x660
__driver_probe_device+0x106/0x240
device_driver_attach+0x7d/0x110
bind_store+0x95/0xe0
kernfs_fop_write_iter+0x1e0/0x280
vfs_write+0x469/0x810
ksys_write+0xd2/0x170
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Last potentially related work creation:
kasan_save_stack+0x33/0x60
kasan_record_aux_stack+0x8c/0xa0
__queue_work+0x661/0xa90
queue_work_on+0x5f/0xb0
unlink1+0x210/0x220
usb_hcd_unlink_urb+0xb8/0x120
usb_kill_urb.part.0+0x96/0x1c0
hub_quiesce+0xfa/0x160
hub_suspend+0x292/0x4f0
usb_suspend_both+0x170/0x4c0
usb_runtime_suspend+0x30/0x90
__rpm_callback+0x67/0x290
rpm_callback+0xab/0xc0
rpm_suspend+0x1c2/0x970
__pm_runtime_suspend+0x3d/0x1d0
usb_new_device+0x645/0x870
register_root_hub+0x146/0x320
usb_add_hcd+0x726/0xbd0
vhci_hcd_probe+0xf1/0x250
platform_probe+0x69/0xe0
really_probe+0x163/0x660
__driver_probe_device+0x106/0x240
device_driver_attach+0x7d/0x110
bind_store+0x95/0xe0
kernfs_fop_write_iter+0x1e0/0x280
vfs_write+0x469/0x810
ksys_write+0xd2/0x170
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Second to last potentially related work creation:
kasan_save_stack+0x33/0x60
kasan_record_aux_stack+0x8c/0xa0
__queue_work+0x661/0xa90
queue_work_on+0x5f/0xb0
usb_hcd_submit_urb+0x41c/0xf30
usb_start_wait_urb+0xd8/0x2d0
usb_control_msg+0x1c6/0x250
hub_suspend+0x37d/0x4f0
usb_suspend_both+0x170/0x4c0
usb_runtime_suspend+0x30/0x90
__rpm_callback+0x67/0x290
rpm_callback+0xab/0xc0
rpm_suspend+0x1c2/0x970
__pm_runtime_suspend+0x3d/0x1d0
usb_new_device+0x645/0x870
register_root_hub+0x146/0x320
usb_add_hcd+0x726/0xbd0
vhci_hcd_probe+0xf1/0x250
platform_probe+0x69/0xe0
really_probe+0x163/0x660
__driver_probe_device+0x106/0x240
device_driver_attach+0x7d/0x110
bind_store+0x95/0xe0
kernfs_fop_write_iter+0x1e0/0x280
vfs_write+0x469/0x810
ksys_write+0xd2/0x170
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff8880095f4000 which
belongs to the cache kmalloc-8k of size 8192.
The buggy address is located 1144 bytes inside of freed 8192-byte
region [ffff8880095f4000, ffff8880095f6000).

A simplified ordering is:

VHCI bind/test-remove path sysfs reader
vhci_hcd_probe() cat vhci_hcd.0/status
usb_add_hcd() for SS HCD
test remove runs
vhci_hcd_remove()
usb_remove_hcd()
usb_put_hcd() frees SS HCD status_show() walks SS state

Reproducer, run as root on a KASAN kernel with CONFIG_USBIP_VHCI_HCD
and CONFIG_DEBUG_TEST_DRIVER_REMOVE enabled:

set -e
modprobe vhci_hcd || true

status=/sys/devices/platform/vhci_hcd.0/status
bind=/sys/bus/platform/drivers/vhci_hcd/bind
unbind=/sys/bus/platform/drivers/vhci_hcd/unbind
dev=vhci_hcd.0

if [ -e "$status" ]; then
echo "$dev" > "$unbind" 2>/dev/null || true
fi

(
deadline=$((SECONDS + 30))
while [ ! -e "$status" ]; do
[ "$SECONDS" -lt "$deadline" ] || exit 1
sleep 0.1
done

for i in $(seq 1 50000); do
[ -e "$status" ] || break
cat "$status" >/dev/null 2>&1 || true
done
) &

reader=$!

sleep 0.2
echo "$dev" > "$bind"

wait "$reader"

This looks like the vhci sysfs group needs to be published only after all
state reachable from the callbacks is live, and removed before any of that
state is removed or put.

Thanks,
Cen