Bug Analysis and Technical Breakdown
Root Cause
The memory leak is caused by improper lifecycle management of buffered transmit packets (sk_buff) in the Option HSO USB network driver (drivers/net/usb/hso.c).
Specifically:
- When a network packet is transmitted via
hso_net_start_xmit() (drivers/net/usb/hso.c:766), the driver checks device activity via hso_get_activity(odev->parent).
hso_get_activity() returns -EAGAIN if the underlying USB device is not in USB_STATE_CONFIGURED (e.g., when the device is suspended, disconnected, or tearing down: udev->state = USB_STATE_NOTATTACHED).
- On
-EAGAIN, hso_net_start_xmit() buffers the packet in odev->skb_tx_buf and returns NETDEV_TX_OK:
if (hso_get_activity(odev->parent) == -EAGAIN) {
odev->skb_tx_buf = skb;
return NETDEV_TX_OK;
}
- By returning
NETDEV_TX_OK, the driver assumes sole ownership of skb. However, drivers/net/usb/hso.c fails to free odev->skb_tx_buf:
- Neither
hso_net_close() nor hso_free_net_device() checks or calls dev_kfree_skb(odev->skb_tx_buf) before free_netdev(hso_net->net) frees struct hso_net.
- Additionally,
hso_net_open() resets odev->skb_tx_buf = NULL without freeing any previous buffer.
Trigger Mechanism
In the reproducer, a virtual USB device handled by hso is attached and its network interface is registered. Duplicate Address Detection (DAD) for IPv6 triggers addrconf_dad_work() -> addrconf_dad_completed() -> ndisc_send_rs() to send an ICMPv6 Router Solicitation packet. When the USB device disconnects or shuts down while this frame is queued, hso_get_activity() returns -EAGAIN. The Router Solicitation skb is stored into odev->skb_tx_buf. During driver teardown (hso_disconnect() -> hso_free_interface() -> hso_free_net_device()), the skb and its allocated data head buffer (kmalloc_reserve) are never released, triggering kmemleak reports for both objects.
Affected Components
- File:
drivers/net/usb/hso.c
- Functions:
hso_net_start_xmit(), hso_net_close(), hso_free_net_device(), hso_net_open()
- Structs & Fields:
struct hso_net::skb_tx_buf