| 0/0 |
2026/05/30 15:41 |
flow |
assessment-security |
1h07m
Results: map[DenialOfService:true Explanation:The bug report describes a hung task in the USB hub worker thread
(`kworker/1:0`), specifically blocking in `i2c_del_adapter()`.
### Cause of the Bug
This is a known architectural limitation in the Linux I2C subsystem, which is
explicitly documented with a `FIXME` comment in `i2c_del_adapter()` (in
`drivers/i2c/i2c-core-base.c`):
```c
/* wait until all references to the device are gone
*
* FIXME: This is old code and should ideally be replaced by an
* alternative which results in decoupling the lifetime of the struct
* device from the i2c_adapter, like spi or netdev do. Any solution
* should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
*/
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released);
```
When an I2C adapter is unregistered, `i2c_del_adapter()` intentionally blocks
and waits for all references to the `struct device` to be dropped.
In the provided reproducer:
1. A virtual USB device (Intel CE9500 DVB) is connected via the `raw-gadget`
interface.
2. The kernel starts probing the device (`dvb_usbv2_probe()`), which registers a
new I2C adapter.
3. Concurrently, the reproducer opens the newly created `/dev/i2c-#` character
device node and intentionally keeps the file descriptor open. Opening this
device node acquires a reference to the I2C adapter's `struct device`.
4. The USB device probe eventually fails (due to missing endpoints/firmware in
the simulated device), triggering the error path which calls
`i2c_del_adapter()`.
5. `i2c_del_adapter()` blocks indefinitely in `wait_for_completion()` because
the user-space process is still holding the file descriptor open, preventing the
device reference count from reaching zero.
6. Because this happens in the context of the USB hub worker thread
(`hub_event`), the worker is blocked, preventing the system from processing any
further USB plug/unplug events.
### Security Impact
While this bug can result in a Denial of Service (DoS) by hanging the USB
subsystem, **it is not a security vulnerability** for the following reasons:
* **Restricted Access:** To trigger this hang, a process must hold a reference
to the I2C adapter. The only way to do this from user-space is by opening the
`/dev/i2c-#` device node. By default, these device nodes are strictly owned by
`root` (or a privileged `i2c` group) with `0600` or `0660` permissions.
* **Unprivileged Users:** A typical unprivileged user cannot open `/dev/i2c-#`
and therefore cannot trigger this bug.
* **User Namespaces:** User namespaces do not grant access to restricted device
nodes in the initial namespace, so the bug cannot be exploited from within a
container/namespace either.
Since triggering this requires root or equivalent privileges (which already have
the ability to cause a DoS), this does not cross any security boundaries. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/30 15:41 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
static unsigned long long procid;
static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}
#define MAX_FDS 30
#define USB_MAX_IFACE_NUM 4
#define USB_MAX_EP_NUM 32
#define USB_MAX_FDS 6
struct usb_endpoint_index {
struct usb_endpoint_descriptor desc;
int handle;
};
struct usb_iface_index {
struct usb_interface_descriptor* iface;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bInterfaceClass;
struct usb_endpoint_index eps[USB_MAX_EP_NUM];
int eps_num;
};
struct usb_device_index {
struct usb_device_descriptor* dev;
struct usb_config_descriptor* config;
uint8_t bDeviceClass;
uint8_t bMaxPower;
int config_length;
struct usb_iface_index ifaces[USB_MAX_IFACE_NUM];
int ifaces_num;
int iface_cur;
};
struct usb_info {
int fd;
struct usb_device_index index;
};
static struct usb_info usb_devices[USB_MAX_FDS];
static struct usb_device_index* lookup_usb_index(int fd)
{
for (int i = 0; i < USB_MAX_FDS; i++) {
if (__atomic_load_n(&usb_devices[i].fd, __ATOMIC_ACQUIRE) == fd)
return &usb_devices[i].index;
}
return NULL;
}
static int usb_devices_num;
static bool parse_usb_descriptor(const char* buffer, size_t length, struct usb_device_index* index)
{
if (length < sizeof(*index->dev) + sizeof(*index->config))
return false;
memset(index, 0, sizeof(*index));
index->dev = (struct usb_device_descriptor*)buffer;
index->config = (struct usb_config_descriptor*)(buffer + sizeof(*index->dev));
index->bDeviceClass = index->dev->bDeviceClass;
index->bMaxPower = index->config->bMaxPower;
index->config_length = length - sizeof(*index->dev);
index->iface_cur = -1;
size_t offset = 0;
while (true) {
if (offset + 1 >= length)
break;
uint8_t desc_length = buffer[offset];
uint8_t desc_type = buffer[offset + 1];
if (desc_length <= 2)
break;
if (offset + desc_length > length)
break;
if (desc_type == USB_DT_INTERFACE && index->ifaces_num < USB_MAX_IFACE_NUM) {
struct usb_interface_descriptor* iface = (struct usb_interface_descriptor*)(buffer + offset);
index->ifaces[index->ifaces_num].iface = iface;
index->ifaces[index->ifaces_num].bInterfaceNumber = iface->bInterfaceNumber;
index->ifaces[index->ifaces_num].bAlternateSetting = iface->bAlternateSetting;
index->ifaces[index->ifaces_num].bInterfaceClass = iface->bInterfaceClass;
index->ifaces_num++;
}
if (desc_type == USB_DT_ENDPOINT && index->ifaces_num > 0) {
struct usb_iface_index* iface = &index->ifaces[index->ifaces_num - 1];
if (iface->eps_num < USB_MAX_EP_NUM) {
memcpy(&iface->eps[iface->eps_num].desc, buffer + offset, sizeof(iface->eps[iface->eps_num].desc));
iface->eps_num++;
}
}
offset += desc_length;
}
return true;
}
static struct usb_device_index* add_usb_index(int fd, const char* dev, size_t dev_len)
{
int i = __atomic_fetch_add(&usb_devices_num, 1, __ATOMIC_RELAXED);
if (i >= USB_MAX_FDS)
return NULL;
if (!parse_usb_descriptor(dev, dev_len, &usb_devices[i].index))
return NULL;
__atomic_store_n(&usb_devices[i].fd, fd, __ATOMIC_RELEASE);
return &usb_devices[i].index;
}
struct vusb_connect_string_descriptor {
uint32_t len;
char* str;
} __attribute__((packed));
struct vusb_connect_descriptors {
uint32_t qual_len;
char* qual;
uint32_t bos_len;
char* bos;
uint32_t strs_len;
struct vusb_connect_string_descriptor strs[0];
} __attribute__((packed));
static const char default_string[] = {
8, USB_DT_STRING,
's', 0, 'y', 0, 'z', 0
};
static const char default_lang_id[] = {
4, USB_DT_STRING,
0x09, 0x04
};
static bool lookup_connect_response_in(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl,
struct usb_qualifier_descriptor* qual,
char** response_data, uint32_t* response_length)
{
struct usb_device_index* index = lookup_usb_index(fd);
uint8_t str_idx;
if (!index)
return false;
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_GET_DESCRIPTOR:
switch (ctrl->wValue >> 8) {
case USB_DT_DEVICE:
*response_data = (char*)index->dev;
*response_length = sizeof(*index->dev);
return true;
case USB_DT_CONFIG:
*response_data = (char*)index->config;
*response_length = index->config_length;
return true;
case USB_DT_STRING:
str_idx = (uint8_t)ctrl->wValue;
if (descs && str_idx < descs->strs_len) {
*response_data = descs->strs[str_idx].str;
*response_length = descs->strs[str_idx].len;
return true;
}
if (str_idx == 0) {
*response_data = (char*)&default_lang_id[0];
*response_length = default_lang_id[0];
return true;
}
*response_data = (char*)&default_string[0];
*response_length = default_string[0];
return true;
case USB_DT_BOS:
*response_data = descs->bos;
*response_length = descs->bos_len;
return true;
case USB_DT_DEVICE_QUALIFIER:
if (!descs->qual) {
qual->bLength = sizeof(*qual);
qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
qual->bcdUSB = index->dev->bcdUSB;
qual->bDeviceClass = index->dev->bDeviceClass;
qual->bDeviceSubClass = index->dev->bDeviceSubClass;
qual->bDeviceProtocol = index->dev->bDeviceProtocol;
qual->bMaxPacketSize0 = index->dev->bMaxPacketSize0;
qual->bNumConfigurations = index->dev->bNumConfigurations;
qual->bRESERVED = 0;
*response_data = (char*)qual;
*response_length = sizeof(*qual);
return true;
}
*response_data = descs->qual;
*response_length = descs->qual_len;
return true;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return false;
}
typedef bool (*lookup_connect_out_response_t)(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done);
static bool lookup_connect_response_out_generic(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done)
{
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_SET_CONFIGURATION:
*done = true;
return true;
default:
break;
}
break;
}
return false;
}
#define UDC_NAME_LENGTH_MAX 128
struct usb_raw_init {
__u8 driver_name[UDC_NAME_LENGTH_MAX];
__u8 device_name[UDC_NAME_LENGTH_MAX];
__u8 speed;
};
enum usb_raw_event_type {
USB_RAW_EVENT_INVALID = 0,
USB_RAW_EVENT_CONNECT = 1,
USB_RAW_EVENT_CONTROL = 2,
};
struct usb_raw_event {
__u32 type;
__u32 length;
__u8 data[0];
};
struct usb_raw_ep_io {
__u16 ep;
__u16 flags;
__u32 length;
__u8 data[0];
};
#define USB_RAW_EPS_NUM_MAX 30
#define USB_RAW_EP_NAME_MAX 16
#define USB_RAW_EP_ADDR_ANY 0xff
struct usb_raw_ep_caps {
__u32 type_control : 1;
__u32 type_iso : 1;
__u32 type_bulk : 1;
__u32 type_int : 1;
__u32 dir_in : 1;
__u32 dir_out : 1;
};
struct usb_raw_ep_limits {
__u16 maxpacket_limit;
__u16 max_streams;
__u32 reserved;
};
struct usb_raw_ep_info {
__u8 name[USB_RAW_EP_NAME_MAX];
__u32 addr;
struct usb_raw_ep_caps caps;
struct usb_raw_ep_limits limits;
};
struct usb_raw_eps_info {
struct usb_raw_ep_info eps[USB_RAW_EPS_NUM_MAX];
};
#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init)
#define USB_RAW_IOCTL_RUN _IO('U', 1)
#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event)
#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor)
#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32)
#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9)
#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32)
#define USB_RAW_IOCTL_EPS_INFO _IOR('U', 11, struct usb_raw_eps_info)
#define USB_RAW_IOCTL_EP0_STALL _IO('U', 12)
#define USB_RAW_IOCTL_EP_SET_HALT _IOW('U', 13, __u32)
#define USB_RAW_IOCTL_EP_CLEAR_HALT _IOW('U', 14, __u32)
#define USB_RAW_IOCTL_EP_SET_WEDGE _IOW('U', 15, __u32)
static int usb_raw_open()
{
return open("/dev/raw-gadget", O_RDWR);
}
static int usb_raw_init(int fd, uint32_t speed, const char* driver, const char* device)
{
struct usb_raw_init arg;
strncpy((char*)&arg.driver_name[0], driver, sizeof(arg.driver_name));
strncpy((char*)&arg.device_name[0], device, sizeof(arg.device_name));
arg.speed = speed;
return ioctl(fd, USB_RAW_IOCTL_INIT, &arg);
}
static int usb_raw_run(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_RUN, 0);
}
static int usb_raw_configure(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0);
}
static int usb_raw_vbus_draw(int fd, uint32_t power)
{
return ioctl(fd, USB_RAW_IOCTL_VBUS_DRAW, power);
}
static int usb_raw_ep0_write(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io);
}
static int usb_raw_ep0_read(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_READ, io);
}
static int usb_raw_event_fetch(int fd, struct usb_raw_event* event)
{
return ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event);
}
static int usb_raw_ep_enable(int fd, struct usb_endpoint_descriptor* desc)
{
return ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, desc);
}
static int usb_raw_ep_disable(int fd, int ep)
{
return ioctl(fd, USB_RAW_IOCTL_EP_DISABLE, ep);
}
static int usb_raw_ep0_stall(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0);
}
#define USB_MAX_PACKET_SIZE 4096
struct usb_raw_control_event {
struct usb_raw_event inner;
struct usb_ctrlrequest ctrl;
char data[USB_MAX_PACKET_SIZE];
};
struct usb_raw_ep_io_data {
struct usb_raw_ep_io inner;
char data[USB_MAX_PACKET_SIZE];
};
static void set_interface(int fd, int n)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return;
if (index->iface_cur >= 0 && index->iface_cur < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[index->iface_cur].eps_num; ep++) {
int rv = usb_raw_ep_disable(fd, index->ifaces[index->iface_cur].eps[ep].handle);
if (rv < 0) {
} else {
}
}
}
if (n >= 0 && n < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[n].eps_num; ep++) {
int rv = usb_raw_ep_enable(fd, &index->ifaces[n].eps[ep].desc);
if (rv < 0) {
} else {
index->ifaces[n].eps[ep].handle = rv;
}
}
index->iface_cur = n;
}
}
static int configure_device(int fd)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
int rv = usb_raw_vbus_draw(fd, index->bMaxPower);
if (rv < 0) {
return rv;
}
rv = usb_raw_configure(fd);
if (rv < 0) {
return rv;
}
set_interface(fd, 0);
return 0;
}
static volatile long syz_usb_connect_impl(uint64_t speed, uint64_t dev_len, const char* dev,
const struct vusb_connect_descriptors* descs,
lookup_connect_out_response_t lookup_connect_response_out)
{
if (!dev) {
return -1;
}
int fd = usb_raw_open();
if (fd < 0) {
return fd;
}
if (fd >= MAX_FDS) {
close(fd);
return -1;
}
struct usb_device_index* index = add_usb_index(fd, dev, dev_len);
if (!index) {
return -1;
}
char device[32];
sprintf(&device[0], "dummy_udc.%llu", procid);
int rv = usb_raw_init(fd, speed, "dummy_udc", &device[0]);
if (rv < 0) {
return rv;
}
rv = usb_raw_run(fd);
if (rv < 0) {
return rv;
}
bool done = false;
while (!done) {
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = sizeof(event.ctrl);
rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL)
continue;
char* response_data = NULL;
uint32_t response_length = 0;
struct usb_qualifier_descriptor qual;
if (event.ctrl.bRequestType & USB_DIR_IN) {
if (!lookup_connect_response_in(fd, descs, &event.ctrl, &qual, &response_data, &response_length)) {
usb_raw_ep0_stall(fd);
continue;
}
} else {
if (!lookup_connect_response_out(fd, descs, &event.ctrl, &done)) {
usb_raw_ep0_stall(fd);
continue;
}
response_data = NULL;
response_length = event.ctrl.wLength;
}
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
event.ctrl.bRequest == USB_REQ_SET_CONFIGURATION) {
rv = configure_device(fd);
if (rv < 0) {
return rv;
}
}
struct usb_raw_ep_io_data response;
response.inner.ep = 0;
response.inner.flags = 0;
if (response_length > sizeof(response.data))
response_length = 0;
if (event.ctrl.wLength < response_length)
response_length = event.ctrl.wLength;
response.inner.length = response_length;
if (response_data)
memcpy(&response.data[0], response_data, response_length);
else
memset(&response.data[0], 0, response_length);
if (event.ctrl.bRequestType & USB_DIR_IN) {
rv = usb_raw_ep0_write(fd, (struct usb_raw_ep_io*)&response);
} else {
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io*)&response);
}
if (rv < 0) {
return rv;
}
}
sleep_ms(200);
return fd;
}
static volatile long syz_usb_connect(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
{
uint64_t speed = a0;
uint64_t dev_len = a1;
const char* dev = (const char*)a2;
const struct vusb_connect_descriptors* descs = (const struct vusb_connect_descriptors*)a3;
return syz_usb_connect_impl(speed, dev_len, dev, descs, &lookup_connect_response_out_generic);
}
static long syz_open_dev(volatile long a0, volatile long a1, volatile long a2)
{
if (a0 == 0xc || a0 == 0xb) {
char buf[128];
sprintf(buf, "/dev/%s/%d:%d", a0 == 0xc ? "char" : "block", (uint8_t)a1, (uint8_t)a2);
return open(buf, O_RDWR, 0);
} else {
unsigned long nb = a1;
char buf[1024];
char* hash;
strncpy(buf, (char*)a0, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
while ((hash = strchr(buf, '#'))) {
*hash = '0' + (char)(nb % 10);
nb /= 10;
}
return open(buf, a2 & ~O_CREAT, 0);
}
}
uint64_t r[2] = {0xffffffffffffffff, 0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_usb_connect arguments: [
// speed: usb_device_speed = 0x0 (8 bytes)
// dev_len: len = 0x24 (8 bytes)
// dev: ptr[in, usb_device_descriptor] {
// usb_device_descriptor {
// inner: usb_device_descriptor_verbose_t[flags[usb_versions, int16], 0, 0, 0, flags[usb_device_max_packet_sizes, int8], 0, 0, 0, array[usb_config_descriptor, 1]] {
// bLength: const = 0x12 (1 bytes)
// bDescriptorType: const = 0x1 (1 bytes)
// bcdUSB: usb_versions = 0x0 (2 bytes)
// bDeviceClass: const = 0x8e (1 bytes)
// bDeviceSubClass: const = 0x88 (1 bytes)
// bDeviceProtocol: const = 0x5 (1 bytes)
// bMaxPacketSize0: usb_device_max_packet_sizes = 0x20 (1 bytes)
// idVendor: const = 0x8086 (2 bytes)
// idProduct: const = 0x9500 (2 bytes)
// bcdDevice: const = 0xb6d8 (2 bytes)
// iManufacturer: const = 0x1 (1 bytes)
// iProduct: const = 0x2 (1 bytes)
// iSerialNumber: const = 0x3 (1 bytes)
// bNumConfigurations: len = 0x1 (1 bytes)
// configs: array[usb_config_descriptor] {
// usb_config_descriptor {
// inner: usb_config_descriptor_verbose_t[int8, len[interfaces, int8], int8, flags[usb_config_attributes, int8], int8, void, array[usb_interface_descriptor, 1:4]] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x2 (1 bytes)
// wTotalLength: len = 0x12 (2 bytes)
// bNumInterfaces: len = 0x1 (1 bytes)
// bConfigurationValue: int8 = 0x0 (1 bytes)
// iConfiguration: int8 = 0x0 (1 bytes)
// bmAttributes: usb_config_attributes = 0x0 (1 bytes)
// bMaxPower: int8 = 0x0 (1 bytes)
// extra: buffer: {} (length 0x0)
// interfaces: array[usb_interface_descriptor] {
// }
// }
// }
// }
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb
*(uint8_t*)0x200000000000 = 0x12;
*(uint8_t*)0x200000000001 = 1;
*(uint16_t*)0x200000000002 = 0;
*(uint8_t*)0x200000000004 = 0x8e;
*(uint8_t*)0x200000000005 = 0x88;
*(uint8_t*)0x200000000006 = 5;
*(uint8_t*)0x200000000007 = 0x20;
*(uint16_t*)0x200000000008 = 0x8086;
*(uint16_t*)0x20000000000a = 0x9500;
*(uint16_t*)0x20000000000c = 0xb6d8;
*(uint8_t*)0x20000000000e = 1;
*(uint8_t*)0x20000000000f = 2;
*(uint8_t*)0x200000000010 = 3;
*(uint8_t*)0x200000000011 = 1;
*(uint8_t*)0x200000000012 = 9;
*(uint8_t*)0x200000000013 = 2;
*(uint16_t*)0x200000000014 = 0x12;
*(uint8_t*)0x200000000016 = 1;
*(uint8_t*)0x200000000017 = 0;
*(uint8_t*)0x200000000018 = 0;
*(uint8_t*)0x200000000019 = 0;
*(uint8_t*)0x20000000001a = 0;
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x24, /*dev=*/0x200000000000, /*conn_descs=*/0);
// syz_open_dev$I2C arguments: [
// dev: ptr[in, buffer] {
// buffer: {2f 64 65 76 2f 69 32 63 2d 23 00} (length 0xb)
// }
// id: intptr = 0x1 (8 bytes)
// flags: open_flags = 0x402 (8 bytes)
// ]
// returns fd_i2c
memcpy((void*)0x200000000000, "/dev/i2c-#\000", 11);
syz_open_dev(/*dev=*/0x200000000000, /*id=*/1, /*flags=O_APPEND|O_RDWR*/0x402);
// pipe2$9p arguments: [
// pipefd: ptr[out, pipe_9p] {
// pipe_9p {
// rfd: rfd9p (resource)
// wfd: wfd9p (resource)
// }
// }
// flags: pipe_flags = 0x0 (8 bytes)
// ]
res = syscall(__NR_pipe2, /*pipefd=*/0x200000000080ul, /*flags=*/0ul);
if (res != -1)
r[0] = *(uint32_t*)0x200000000084;
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: open_flags = 0x101042 (4 bytes)
// mode: open_mode = 0x0 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, "./file1\000", 8);
res = syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000040ul, /*flags=O_SYNC|O_CREAT|O_RDWR*/0x101042, /*mode=*/0);
if (res != -1)
r[1] = res;
// mount$9p_fd arguments: [
// src: const = 0x0 (8 bytes)
// dst: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// type: ptr[in, buffer] {
// buffer: {39 70 00} (length 0x3)
// }
// flags: mount_flags = 0x0 (8 bytes)
// opts: ptr[in, p9_options_fd] {
// p9_options_fd {
// trans: buffer: {74 72 61 6e 73 3d 66 64 2c} (length 0x9)
// rfdno: fs_opt["rfdno", fmt[hex, rfd9p]] {
// name: buffer: {72 66 64 6e 6f} (length 0x5)
// eq: const = 0x3d (1 bytes)
// val: rfd9p (resource)
// }
// comma0: const = 0x2c (1 bytes)
// wfdno: fs_opt["wfdno", fmt[hex, fd]] {
// name: buffer: {77 66 64 6e 6f} (length 0x5)
// eq: const = 0x3d (1 bytes)
// val: fd (resource)
// }
// comma1: const = 0x2c (1 bytes)
// opts: fs_options[p9_options] {
// elems: array[fs_opt_elem[p9_options]] {
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// }
// ]
memcpy((void*)0x200000000000, "./file1\000", 8);
memcpy((void*)0x200000000100, "9p\000", 3);
memcpy((void*)0x200000000300, "trans=fd,", 9);
memcpy((void*)0x200000000309, "rfdno", 5);
*(uint8_t*)0x20000000030e = 0x3d;
sprintf((char*)0x20000000030f, "0x%016llx", (long long)r[1]);
*(uint8_t*)0x200000000321 = 0x2c;
memcpy((void*)0x200000000322, "wfdno", 5);
*(uint8_t*)0x200000000327 = 0x3d;
sprintf((char*)0x200000000328, "0x%016llx", (long long)r[0]);
*(uint8_t*)0x20000000033a = 0x2c;
*(uint8_t*)0x20000000033b = 0;
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x200000000000ul, /*type=*/0x200000000100ul, /*flags=*/0ul, /*opts=*/0x200000000300ul);
return 0;
}
]
|
| 2/1 |
2026/05/30 15:41 |
action |
kernel-checkouter |
2m
Results: map[KernelSrc:/app/workdir/cache/src/2427e97992108cbb6fab7c96efe24fc815750e59]
|
| 3/1 |
2026/05/30 15:43 |
action |
kernel-builder |
32m
Results: map[KernelObj:/app/workdir/cache/build/5aeee7a342e899f9326efec020c4f7662d6e126e]
|
| 4/1 |
2026/05/30 16:15 |
action |
codesearch-prepare |
22m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/30 16:38 |
agent |
expert |
10m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false 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 or elevated privileges.
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.
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:
INFO: task kworker/1:0:24 blocked for more than 143 seconds.
Not tainted 6.15.0-syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:kworker/1:0 state:D stack:21448 pid:24 tgid:24 ppid:2 task_flags:0x4208060 flags:0x00004000
Workqueue: usb_hub_wq hub_event
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5382 [inline]
__schedule+0x168f/0x4c70 kernel/sched/core.c:6767
__schedule_loop kernel/sched/core.c:6845 [inline]
schedule+0x165/0x360 kernel/sched/core.c:6860
schedule_timeout+0x9a/0x270 kernel/time/sleep_timeout.c:75
do_wait_for_common kernel/sched/completion.c:95 [inline]
__wait_for_common kernel/sched/completion.c:116 [inline]
wait_for_common kernel/sched/completion.c:127 [inline]
wait_for_completion+0x2bf/0x5d0 kernel/sched/completion.c:148
i2c_del_adapter+0x581/0x6e0 drivers/i2c/i2c-core-base.c:1812
dvb_usbv2_probe+0x4ae/0x41a0 drivers/media/usb/dvb-usb-v2/dvb_usb_core.c:993
usb_probe_interface+0x641/0xbc0 drivers/usb/core/driver.c:396
call_driver_probe drivers/base/dd.c:-1 [inline]
really_probe+0x26d/0x9a0 drivers/base/dd.c:657
__driver_probe_device+0x18c/0x2f0 drivers/base/dd.c:799
driver_probe_device+0x4f/0x430 drivers/base/dd.c:829
__device_attach_driver+0x2ce/0x530 drivers/base/dd.c:957
bus_for_each_drv+0x24e/0x2e0 drivers/base/bus.c:462
__device_attach+0x2b8/0x400 drivers/base/dd.c:1029
bus_probe_device+0x185/0x260 drivers/base/bus.c:537
device_add+0x7b6/0xb50 drivers/base/core.c:3692
usb_set_configuration+0x1a87/0x20e0 drivers/usb/core/message.c:2210
usb_generic_driver_probe+0x8d/0x150 drivers/usb/core/generic.c:250
usb_probe_device+0x1c1/0x390 drivers/usb/core/driver.c:291
call_driver_probe drivers/base/dd.c:-1 [inline]
really_probe+0x26d/0x9a0 drivers/base/dd.c:657
__driver_probe_device+0x18c/0x2f0 drivers/base/dd.c:799
driver_probe_device+0x4f/0x430 drivers/base/dd.c:829
__device_attach_driver+0x2ce/0x530 drivers/base/dd.c:957
bus_for_each_drv+0x24e/0x2e0 drivers/base/bus.c:462
__device_attach+0x2b8/0x400 drivers/base/dd.c:1029
bus_probe_device+0x185/0x260 drivers/base/bus.c:537
device_add+0x7b6/0xb50 drivers/base/core.c:3692
usb_new_device+0xa39/0x16c0 drivers/usb/core/hub.c:2663
hub_port_connect drivers/usb/core/hub.c:5531 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5671 [inline]
port_event drivers/usb/core/hub.c:5831 [inline]
hub_event+0x2941/0x4a00 drivers/usb/core/hub.c:5913
process_one_work kernel/workqueue.c:3238 [inline]
process_scheduled_works+0xade/0x17a0 kernel/workqueue.c:3319
worker_thread+0x8a0/0xda0 kernel/workqueue.c:3400
kthread+0x711/0x8a0 kernel/kthread.c:464
ret_from_fork+0x4b/0x80 arch/x86/kernel/process.c:153
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Showing all locks held in the system:
5 locks held by kworker/1:0/24:
#0: ffff888021282148 ((wq_completion)usb_hub_wq){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3213 [inline]
#0: ffff888021282148 ((wq_completion)usb_hub_wq){+.+.}-{0:0}, at: process_scheduled_works+0x9b1/0x17a0 kernel/workqueue.c:3319
#1: ffffc900001e7c60 ((work_completion)(&hub->events)){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3214 [inline]
#1: ffffc900001e7c60 ((work_completion)(&hub->events)){+.+.}-{0:0}, at: process_scheduled_works+0x9ec/0x17a0 kernel/workqueue.c:3319
#2: ffff88802805d198 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:922 [inline]
#2: ffff88802805d198 (&dev->mutex){....}-{4:4}, at: hub_event+0x184/0x4a00 drivers/usb/core/hub.c:5859
#3: ffff8880279db198 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:922 [inline]
#3: ffff8880279db198 (&dev->mutex){....}-{4:4}, at: __device_attach+0x88/0x400 drivers/base/dd.c:1004
#4: ffff8880279de160 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:922 [inline]
#4: ffff8880279de160 (&dev->mutex){....}-{4:4}, at: __device_attach+0x88/0x400 drivers/base/dd.c:1004
1 lock held by khungtaskd/31:
#0: ffffffff8df3dee0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:331 [inline]
#0: ffffffff8df3dee0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:841 [inline]
#0: ffffffff8df3dee0 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x2e/0x180 kernel/locking/lockdep.c:6764
2 locks held by getty/5582:
#0: ffff88814c8770a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x25/0x70 drivers/tty/tty_ldisc.c:243
#1: ffffc9000333b2f0 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x43e/0x1400 drivers/tty/n_tty.c:2222
4 locks held by udevd/5836:
#0: ffff888033995e80 (&p->lock){+.+.}-{4:4}, at: seq_read_iter+0xb7/0xe10 fs/seq_file.c:182
#1: ffff888032071c88 (&of->mutex#2){+.+.}-{4:4}, at: kernfs_seq_start+0x55/0x3c0 fs/kernfs/file.c:154
#2: ffff888033fb41e8 (kn->active#26){.+.+}-{0:0}, at: kernfs_seq_start+0x75/0x3c0 fs/kernfs/file.c:155
#3: ffff8880279db198 (&dev->mutex){....}-{4:4}, at: device_lock_interruptible include/linux/device.h:927 [inline]
#3: ffff8880279db198 (&dev->mutex){....}-{4:4}, at: manufacturer_show+0x26/0xa0 drivers/usb/core/sysfs.c:142
=============================================
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 31 Comm: khungtaskd Not tainted 6.15.0-syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/07/2025
Call Trace:
<TASK>
dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120
nmi_cpu_backtrace+0x39e/0x3d0 lib/nmi_backtrace.c:113
nmi_trigger_cpumask_backtrace+0x17a/0x300 lib/nmi_backtrace.c:62
trigger_all_cpu_backtrace include/linux/nmi.h:158 [inline]
check_hung_uninterruptible_tasks kernel/hung_task.c:274 [inline]
watchdog+0xfee/0x1030 kernel/hung_task.c:437
kthread+0x711/0x8a0 kernel/kthread.c:464
ret_from_fork+0x4b/0x80 arch/x86/kernel/process.c:153
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Sending NMI from CPU 1 to CPUs 0:
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.15.0-syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/07/2025
RIP: 0010:pv_native_safe_halt+0x13/0x20 arch/x86/kernel/paravirt.c:81
Code: 43 d4 02 00 cc cc cc 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa 66 90 0f 00 2d 63 7f 18 00 f3 0f 1e fa fb f4 <e9> 18 d4 02 00 cc cc cc cc cc cc cc cc 90 90 90 90 90 90 90 90 90
RSP: 0018:ffffffff8dc07d80 EFLAGS: 000002c6
RAX: 22e8c235b9a2e600 RBX: ffffffff81977028 RCX: 22e8c235b9a2e600
RDX: 0000000000000001 RSI: ffffffff8d73aab2 RDI: ffffffff8bc12240
RBP: ffffffff8dc07ec0 R08: ffff8880b8832b5b R09: 1ffff1101710656b
R10: dffffc0000000000 R11: ffffed101710656c R12: ffffffff8f7e0b70
R13: 0000000000000000 R14: 0000000000000000 R15: 1ffffffff1b92a48
FS: 0000000000000000(0000) GS:ffff8881260f6000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005650a6dfa660 CR3: 000000000dd38000 CR4: 00000000003526f0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
Call Trace:
<TASK>
arch_safe_halt arch/x86/include/asm/paravirt.h:107 [inline]
default_idle+0x13/0x20 arch/x86/kernel/process.c:748
default_idle_call+0x74/0xb0 kernel/sched/idle.c:117
cpuidle_idle_call kernel/sched/idle.c:185 [inline]
do_idle+0x1e8/0x510 kernel/sched/idle.c:325
cpu_startup_entry+0x44/0x60 kernel/sched/idle.c:423
rest_init+0x2de/0x300 init/main.c:743
start_kernel+0x470/0x4f0 init/main.c:1099
x86_64_start_reservations+0x2a/0x30 arch/x86/kernel/head64.c:513
x86_64_start_kernel+0x66/0x70 arch/x86/kernel/head64.c:494
common_startup_64+0x13e/0x147
</TASK>
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.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
static unsigned long long procid;
static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}
#define MAX_FDS 30
#define USB_MAX_IFACE_NUM 4
#define USB_MAX_EP_NUM 32
#define USB_MAX_FDS 6
struct usb_endpoint_index {
struct usb_endpoint_descriptor desc;
int handle;
};
struct usb_iface_index {
struct usb_interface_descriptor* iface;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bInterfaceClass;
struct usb_endpoint_index eps[USB_MAX_EP_NUM];
int eps_num;
};
struct usb_device_index {
struct usb_device_descriptor* dev;
struct usb_config_descriptor* config;
uint8_t bDeviceClass;
uint8_t bMaxPower;
int config_length;
struct usb_iface_index ifaces[USB_MAX_IFACE_NUM];
int ifaces_num;
int iface_cur;
};
struct usb_info {
int fd;
struct usb_device_index index;
};
static struct usb_info usb_devices[USB_MAX_FDS];
static struct usb_device_index* lookup_usb_index(int fd)
{
for (int i = 0; i < USB_MAX_FDS; i++) {
if (__atomic_load_n(&usb_devices[i].fd, __ATOMIC_ACQUIRE) == fd)
return &usb_devices[i].index;
}
return NULL;
}
static int usb_devices_num;
static bool parse_usb_descriptor(const char* buffer, size_t length, struct usb_device_index* index)
{
if (length < sizeof(*index->dev) + sizeof(*index->config))
return false;
memset(index, 0, sizeof(*index));
index->dev = (struct usb_device_descriptor*)buffer;
index->config = (struct usb_config_descriptor*)(buffer + sizeof(*index->dev));
index->bDeviceClass = index->dev->bDeviceClass;
index->bMaxPower = index->config->bMaxPower;
index->config_length = length - sizeof(*index->dev);
index->iface_cur = -1;
size_t offset = 0;
while (true) {
if (offset + 1 >= length)
break;
uint8_t desc_length = buffer[offset];
uint8_t desc_type = buffer[offset + 1];
if (desc_length <= 2)
break;
if (offset + desc_length > length)
break;
if (desc_type == USB_DT_INTERFACE && index->ifaces_num < USB_MAX_IFACE_NUM) {
struct usb_interface_descriptor* iface = (struct usb_interface_descriptor*)(buffer + offset);
index->ifaces[index->ifaces_num].iface = iface;
index->ifaces[index->ifaces_num].bInterfaceNumber = iface->bInterfaceNumber;
index->ifaces[index->ifaces_num].bAlternateSetting = iface->bAlternateSetting;
index->ifaces[index->ifaces_num].bInterfaceClass = iface->bInterfaceClass;
index->ifaces_num++;
}
if (desc_type == USB_DT_ENDPOINT && index->ifaces_num > 0) {
struct usb_iface_index* iface = &index->ifaces[index->ifaces_num - 1];
if (iface->eps_num < USB_MAX_EP_NUM) {
memcpy(&iface->eps[iface->eps_num].desc, buffer + offset, sizeof(iface->eps[iface->eps_num].desc));
iface->eps_num++;
}
}
offset += desc_length;
}
return true;
}
static struct usb_device_index* add_usb_index(int fd, const char* dev, size_t dev_len)
{
int i = __atomic_fetch_add(&usb_devices_num, 1, __ATOMIC_RELAXED);
if (i >= USB_MAX_FDS)
return NULL;
if (!parse_usb_descriptor(dev, dev_len, &usb_devices[i].index))
return NULL;
__atomic_store_n(&usb_devices[i].fd, fd, __ATOMIC_RELEASE);
return &usb_devices[i].index;
}
struct vusb_connect_string_descriptor {
uint32_t len;
char* str;
} __attribute__((packed));
struct vusb_connect_descriptors {
uint32_t qual_len;
char* qual;
uint32_t bos_len;
char* bos;
uint32_t strs_len;
struct vusb_connect_string_descriptor strs[0];
} __attribute__((packed));
static const char default_string[] = {
8, USB_DT_STRING,
's', 0, 'y', 0, 'z', 0
};
static const char default_lang_id[] = {
4, USB_DT_STRING,
0x09, 0x04
};
static bool lookup_connect_response_in(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl,
struct usb_qualifier_descriptor* qual,
char** response_data, uint32_t* response_length)
{
struct usb_device_index* index = lookup_usb_index(fd);
uint8_t str_idx;
if (!index)
return false;
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_GET_DESCRIPTOR:
switch (ctrl->wValue >> 8) {
case USB_DT_DEVICE:
*response_data = (char*)index->dev;
*response_length = sizeof(*index->dev);
return true;
case USB_DT_CONFIG:
*response_data = (char*)index->config;
*response_length = index->config_length;
return true;
case USB_DT_STRING:
str_idx = (uint8_t)ctrl->wValue;
if (descs && str_idx < descs->strs_len) {
*response_data = descs->strs[str_idx].str;
*response_length = descs->strs[str_idx].len;
return true;
}
if (str_idx == 0) {
*response_data = (char*)&default_lang_id[0];
*response_length = default_lang_id[0];
return true;
}
*response_data = (char*)&default_string[0];
*response_length = default_string[0];
return true;
case USB_DT_BOS:
*response_data = descs->bos;
*response_length = descs->bos_len;
return true;
case USB_DT_DEVICE_QUALIFIER:
if (!descs->qual) {
qual->bLength = sizeof(*qual);
qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
qual->bcdUSB = index->dev->bcdUSB;
qual->bDeviceClass = index->dev->bDeviceClass;
qual->bDeviceSubClass = index->dev->bDeviceSubClass;
qual->bDeviceProtocol = index->dev->bDeviceProtocol;
qual->bMaxPacketSize0 = index->dev->bMaxPacketSize0;
qual->bNumConfigurations = index->dev->bNumConfigurations;
qual->bRESERVED = 0;
*response_data = (char*)qual;
*response_length = sizeof(*qual);
return true;
}
*response_data = descs->qual;
*response_length = descs->qual_len;
return true;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return false;
}
typedef bool (*lookup_connect_out_response_t)(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done);
static bool lookup_connect_response_out_generic(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done)
{
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_SET_CONFIGURATION:
*done = true;
return true;
default:
break;
}
break;
}
return false;
}
#define UDC_NAME_LENGTH_MAX 128
struct usb_raw_init {
__u8 driver_name[UDC_NAME_LENGTH_MAX];
__u8 device_name[UDC_NAME_LENGTH_MAX];
__u8 speed;
};
enum usb_raw_event_type {
USB_RAW_EVENT_INVALID = 0,
USB_RAW_EVENT_CONNECT = 1,
USB_RAW_EVENT_CONTROL = 2,
};
struct usb_raw_event {
__u32 type;
__u32 length;
__u8 data[0];
};
struct usb_raw_ep_io {
__u16 ep;
__u16 flags;
__u32 length;
__u8 data[0];
};
#define USB_RAW_EPS_NUM_MAX 30
#define USB_RAW_EP_NAME_MAX 16
#define USB_RAW_EP_ADDR_ANY 0xff
struct usb_raw_ep_caps {
__u32 type_control : 1;
__u32 type_iso : 1;
__u32 type_bulk : 1;
__u32 type_int : 1;
__u32 dir_in : 1;
__u32 dir_out : 1;
};
struct usb_raw_ep_limits {
__u16 maxpacket_limit;
__u16 max_streams;
__u32 reserved;
};
struct usb_raw_ep_info {
__u8 name[USB_RAW_EP_NAME_MAX];
__u32 addr;
struct usb_raw_ep_caps caps;
struct usb_raw_ep_limits limits;
};
struct usb_raw_eps_info {
struct usb_raw_ep_info eps[USB_RAW_EPS_NUM_MAX];
};
#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init)
#define USB_RAW_IOCTL_RUN _IO('U', 1)
#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event)
#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor)
#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32)
#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9)
#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32)
#define USB_RAW_IOCTL_EPS_INFO _IOR('U', 11, struct usb_raw_eps_info)
#define USB_RAW_IOCTL_EP0_STALL _IO('U', 12)
#define USB_RAW_IOCTL_EP_SET_HALT _IOW('U', 13, __u32)
#define USB_RAW_IOCTL_EP_CLEAR_HALT _IOW('U', 14, __u32)
#define USB_RAW_IOCTL_EP_SET_WEDGE _IOW('U', 15, __u32)
static int usb_raw_open()
{
return open("/dev/raw-gadget", O_RDWR);
}
static int usb_raw_init(int fd, uint32_t speed, const char* driver, const char* device)
{
struct usb_raw_init arg;
strncpy((char*)&arg.driver_name[0], driver, sizeof(arg.driver_name));
strncpy((char*)&arg.device_name[0], device, sizeof(arg.device_name));
arg.speed = speed;
return ioctl(fd, USB_RAW_IOCTL_INIT, &arg);
}
static int usb_raw_run(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_RUN, 0);
}
static int usb_raw_configure(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0);
}
static int usb_raw_vbus_draw(int fd, uint32_t power)
{
return ioctl(fd, USB_RAW_IOCTL_VBUS_DRAW, power);
}
static int usb_raw_ep0_write(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io);
}
static int usb_raw_ep0_read(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_READ, io);
}
static int usb_raw_event_fetch(int fd, struct usb_raw_event* event)
{
return ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event);
}
static int usb_raw_ep_enable(int fd, struct usb_endpoint_descriptor* desc)
{
return ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, desc);
}
static int usb_raw_ep_disable(int fd, int ep)
{
return ioctl(fd, USB_RAW_IOCTL_EP_DISABLE, ep);
}
static int usb_raw_ep0_stall(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0);
}
#define USB_MAX_PACKET_SIZE 4096
struct usb_raw_control_event {
struct usb_raw_event inner;
struct usb_ctrlrequest ctrl;
char data[USB_MAX_PACKET_SIZE];
};
struct usb_raw_ep_io_data {
struct usb_raw_ep_io inner;
char data[USB_MAX_PACKET_SIZE];
};
static void set_interface(int fd, int n)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return;
if (index->iface_cur >= 0 && index->iface_cur < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[index->iface_cur].eps_num; ep++) {
int rv = usb_raw_ep_disable(fd, index->ifaces[index->iface_cur].eps[ep].handle);
if (rv < 0) {
} else {
}
}
}
if (n >= 0 && n < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[n].eps_num; ep++) {
int rv = usb_raw_ep_enable(fd, &index->ifaces[n].eps[ep].desc);
if (rv < 0) {
} else {
index->ifaces[n].eps[ep].handle = rv;
}
}
index->iface_cur = n;
}
}
static int configure_device(int fd)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
int rv = usb_raw_vbus_draw(fd, index->bMaxPower);
if (rv < 0) {
return rv;
}
rv = usb_raw_configure(fd);
if (rv < 0) {
return rv;
}
set_interface(fd, 0);
return 0;
}
static volatile long syz_usb_connect_impl(uint64_t speed, uint64_t dev_len, const char* dev,
const struct vusb_connect_descriptors* descs,
lookup_connect_out_response_t lookup_connect_response_out)
{
if (!dev) {
return -1;
}
int fd = usb_raw_open();
if (fd < 0) {
return fd;
}
if (fd >= MAX_FDS) {
close(fd);
return -1;
}
struct usb_device_index* index = add_usb_index(fd, dev, dev_len);
if (!index) {
return -1;
}
char device[32];
sprintf(&device[0], "dummy_udc.%llu", procid);
int rv = usb_raw_init(fd, speed, "dummy_udc", &device[0]);
if (rv < 0) {
return rv;
}
rv = usb_raw_run(fd);
if (rv < 0) {
return rv;
}
bool done = false;
while (!done) {
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = sizeof(event.ctrl);
rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL)
continue;
char* response_data = NULL;
uint32_t response_length = 0;
struct usb_qualifier_descriptor qual;
if (event.ctrl.bRequestType & USB_DIR_IN) {
if (!lookup_connect_response_in(fd, descs, &event.ctrl, &qual, &response_data, &response_length)) {
usb_raw_ep0_stall(fd);
continue;
}
} else {
if (!lookup_connect_response_out(fd, descs, &event.ctrl, &done)) {
usb_raw_ep0_stall(fd);
continue;
}
response_data = NULL;
response_length = event.ctrl.wLength;
}
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
event.ctrl.bRequest == USB_REQ_SET_CONFIGURATION) {
rv = configure_device(fd);
if (rv < 0) {
return rv;
}
}
struct usb_raw_ep_io_data response;
response.inner.ep = 0;
response.inner.flags = 0;
if (response_length > sizeof(response.data))
response_length = 0;
if (event.ctrl.wLength < response_length)
response_length = event.ctrl.wLength;
response.inner.length = response_length;
if (response_data)
memcpy(&response.data[0], response_data, response_length);
else
memset(&response.data[0], 0, response_length);
if (event.ctrl.bRequestType & USB_DIR_IN) {
rv = usb_raw_ep0_write(fd, (struct usb_raw_ep_io*)&response);
} else {
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io*)&response);
}
if (rv < 0) {
return rv;
}
}
sleep_ms(200);
return fd;
}
static volatile long syz_usb_connect(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
{
uint64_t speed = a0;
uint64_t dev_len = a1;
const char* dev = (const char*)a2;
const struct vusb_connect_descriptors* descs = (const struct vusb_connect_descriptors*)a3;
return syz_usb_connect_impl(speed, dev_len, dev, descs, &lookup_connect_response_out_generic);
}
static long syz_open_dev(volatile long a0, volatile long a1, volatile long a2)
{
if (a0 == 0xc || a0 == 0xb) {
char buf[128];
sprintf(buf, "/dev/%s/%d:%d", a0 == 0xc ? "char" : "block", (uint8_t)a1, (uint8_t)a2);
return open(buf, O_RDWR, 0);
} else {
unsigned long nb = a1;
char buf[1024];
char* hash;
strncpy(buf, (char*)a0, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
while ((hash = strchr(buf, '#'))) {
*hash = '0' + (char)(nb % 10);
nb /= 10;
}
return open(buf, a2 & ~O_CREAT, 0);
}
}
uint64_t r[2] = {0xffffffffffffffff, 0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_usb_connect arguments: [
// speed: usb_device_speed = 0x0 (8 bytes)
// dev_len: len = 0x24 (8 bytes)
// dev: ptr[in, usb_device_descriptor] {
// usb_device_descriptor {
// inner: usb_device_descriptor_verbose_t[flags[usb_versions, int16], 0, 0, 0, flags[usb_device_max_packet_sizes, int8], 0, 0, 0, array[usb_config_descriptor, 1]] {
// bLength: const = 0x12 (1 bytes)
// bDescriptorType: const = 0x1 (1 bytes)
// bcdUSB: usb_versions = 0x0 (2 bytes)
// bDeviceClass: const = 0x8e (1 bytes)
// bDeviceSubClass: const = 0x88 (1 bytes)
// bDeviceProtocol: const = 0x5 (1 bytes)
// bMaxPacketSize0: usb_device_max_packet_sizes = 0x20 (1 bytes)
// idVendor: const = 0x8086 (2 bytes)
// idProduct: const = 0x9500 (2 bytes)
// bcdDevice: const = 0xb6d8 (2 bytes)
// iManufacturer: const = 0x1 (1 bytes)
// iProduct: const = 0x2 (1 bytes)
// iSerialNumber: const = 0x3 (1 bytes)
// bNumConfigurations: len = 0x1 (1 bytes)
// configs: array[usb_config_descriptor] {
// usb_config_descriptor {
// inner: usb_config_descriptor_verbose_t[int8, len[interfaces, int8], int8, flags[usb_config_attributes, int8], int8, void, array[usb_interface_descriptor, 1:4]] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x2 (1 bytes)
// wTotalLength: len = 0x12 (2 bytes)
// bNumInterfaces: len = 0x1 (1 bytes)
// bConfigurationValue: int8 = 0x0 (1 bytes)
// iConfiguration: int8 = 0x0 (1 bytes)
// bmAttributes: usb_config_attributes = 0x0 (1 bytes)
// bMaxPower: int8 = 0x0 (1 bytes)
// extra: buffer: {} (length 0x0)
// interfaces: array[usb_interface_descriptor] {
// }
// }
// }
// }
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb
*(uint8_t*)0x200000000000 = 0x12;
*(uint8_t*)0x200000000001 = 1;
*(uint16_t*)0x200000000002 = 0;
*(uint8_t*)0x200000000004 = 0x8e;
*(uint8_t*)0x200000000005 = 0x88;
*(uint8_t*)0x200000000006 = 5;
*(uint8_t*)0x200000000007 = 0x20;
*(uint16_t*)0x200000000008 = 0x8086;
*(uint16_t*)0x20000000000a = 0x9500;
*(uint16_t*)0x20000000000c = 0xb6d8;
*(uint8_t*)0x20000000000e = 1;
*(uint8_t*)0x20000000000f = 2;
*(uint8_t*)0x200000000010 = 3;
*(uint8_t*)0x200000000011 = 1;
*(uint8_t*)0x200000000012 = 9;
*(uint8_t*)0x200000000013 = 2;
*(uint16_t*)0x200000000014 = 0x12;
*(uint8_t*)0x200000000016 = 1;
*(uint8_t*)0x200000000017 = 0;
*(uint8_t*)0x200000000018 = 0;
*(uint8_t*)0x200000000019 = 0;
*(uint8_t*)0x20000000001a = 0;
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x24, /*dev=*/0x200000000000, /*conn_descs=*/0);
// syz_open_dev$I2C arguments: [
// dev: ptr[in, buffer] {
// buffer: {2f 64 65 76 2f 69 32 63 2d 23 00} (length 0xb)
// }
// id: intptr = 0x1 (8 bytes)
// flags: open_flags = 0x402 (8 bytes)
// ]
// returns fd_i2c
memcpy((void*)0x200000000000, "/dev/i2c-#\000", 11);
syz_open_dev(/*dev=*/0x200000000000, /*id=*/1, /*flags=O_APPEND|O_RDWR*/0x402);
// pipe2$9p arguments: [
// pipefd: ptr[out, pipe_9p] {
// pipe_9p {
// rfd: rfd9p (resource)
// wfd: wfd9p (resource)
// }
// }
// flags: pipe_flags = 0x0 (8 bytes)
// ]
res = syscall(__NR_pipe2, /*pipefd=*/0x200000000080ul, /*flags=*/0ul);
if (res != -1)
r[0] = *(uint32_t*)0x200000000084;
// openat arguments: [
// fd: fd_dir (resource)
// file: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// flags: open_flags = 0x101042 (4 bytes)
// mode: open_mode = 0x0 (2 bytes)
// ]
// returns fd
memcpy((void*)0x200000000040, "./file1\000", 8);
res = syscall(__NR_openat, /*fd=*/0xffffff9c, /*file=*/0x200000000040ul, /*flags=O_SYNC|O_CREAT|O_RDWR*/0x101042, /*mode=*/0);
if (res != -1)
r[1] = res;
// mount$9p_fd arguments: [
// src: const = 0x0 (8 bytes)
// dst: ptr[in, buffer] {
// buffer: {2e 2f 66 69 6c 65 31 00} (length 0x8)
// }
// type: ptr[in, buffer] {
// buffer: {39 70 00} (length 0x3)
// }
// flags: mount_flags = 0x0 (8 bytes)
// opts: ptr[in, p9_options_fd] {
// p9_options_fd {
// trans: buffer: {74 72 61 6e 73 3d 66 64 2c} (length 0x9)
// rfdno: fs_opt["rfdno", fmt[hex, rfd9p]] {
// name: buffer: {72 66 64 6e 6f} (length 0x5)
// eq: const = 0x3d (1 bytes)
// val: rfd9p (resource)
// }
// comma0: const = 0x2c (1 bytes)
// wfdno: fs_opt["wfdno", fmt[hex, fd]] {
// name: buffer: {77 66 64 6e 6f} (length 0x5)
// eq: const = 0x3d (1 bytes)
// val: fd (resource)
// }
// comma1: const = 0x2c (1 bytes)
// opts: fs_options[p9_options] {
// elems: array[fs_opt_elem[p9_options]] {
// }
// common: array[fs_opt_elem[fs_options_common]] {
// }
// null: const = 0x0 (1 bytes)
// }
// }
// }
// ]
memcpy((void*)0x200000000000, "./file1\000", 8);
memcpy((void*)0x200000000100, "9p\000", 3);
memcpy((void*)0x200000000300, "trans=fd,", 9);
memcpy((void*)0x200000000309, "rfdno", 5);
*(uint8_t*)0x20000000030e = 0x3d;
sprintf((char*)0x20000000030f, "0x%016llx", (long long)r[1]);
*(uint8_t*)0x200000000321 = 0x2c;
memcpy((void*)0x200000000322, "wfdno", 5);
*(uint8_t*)0x200000000327 = 0x3d;
sprintf((char*)0x200000000328, "0x%016llx", (long long)r[0]);
*(uint8_t*)0x20000000033a = 0x2c;
*(uint8_t*)0x20000000033b = 0;
syscall(__NR_mount, /*src=*/0ul, /*dst=*/0x200000000000ul, /*type=*/0x200000000100ul, /*flags=*/0ul, /*opts=*/0x200000000300ul);
return 0;
}
Reply: The bug report describes a hung task in the USB hub worker thread (`kworker/1:0`), specifically blocking in `i2c_del_adapter()`.
### Cause of the Bug
This is a known architectural limitation in the Linux I2C subsystem, which is explicitly documented with a `FIXME` comment in `i2c_del_adapter()` (in `drivers/i2c/i2c-core-base.c`):
```c
/* wait until all references to the device are gone
*
* FIXME: This is old code and should ideally be replaced by an
* alternative which results in decoupling the lifetime of the struct
* device from the i2c_adapter, like spi or netdev do. Any solution
* should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
*/
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released);
```
When an I2C adapter is unregistered, `i2c_del_adapter()` intentionally blocks and waits for all references to the `struct device` to be dropped.
In the provided reproducer:
1. A virtual USB device (Intel CE9500 DVB) is connected via the `raw-gadget` interface.
2. The kernel starts probing the device (`dvb_usbv2_probe()`), which registers a new I2C adapter.
3. Concurrently, the reproducer opens the newly created `/dev/i2c-#` character device node and intentionally keeps the file descriptor open. Opening this device node acquires a reference to the I2C adapter's `struct device`.
4. The USB device probe eventually fails (due to missing endpoints/firmware in the simulated device), triggering the error path which calls `i2c_del_adapter()`.
5. `i2c_del_adapter()` blocks indefinitely in `wait_for_completion()` because the user-space process is still holding the file descriptor open, preventing the device reference count from reaching zero.
6. Because this happens in the context of the USB hub worker thread (`hub_event`), the worker is blocked, preventing the system from processing any further USB plug/unplug events.
### Security Impact
While this bug can result in a Denial of Service (DoS) by hanging the USB subsystem, **it is not a security vulnerability** for the following reasons:
* **Restricted Access:** To trigger this hang, a process must hold a reference to the I2C adapter. The only way to do this from user-space is by opening the `/dev/i2c-#` device node. By default, these device nodes are strictly owned by `root` (or a privileged `i2c` group) with `0600` or `0660` permissions.
* **Unprivileged Users:** A typical unprivileged user cannot open `/dev/i2c-#` and therefore cannot trigger this bug.
* **User Namespaces:** User namespaces do not grant access to restricted device nodes in the initial namespace, so the bug cannot be exploited from within a container/namespace either.
Since triggering this requires root or equivalent privileges (which already have the ability to cause a DoS), this does not cross any security boundaries.
|
| 6/2 |
2026/05/30 16:38 |
llm |
expert |
1m
Model:
Tokens:
input: 18614
output: 54
thoughts: 597
Thoughts: **Analyzing a Kworker Hang**
Okay, so I've got this bug report, a kworker thread is blocked – that's never good. The "D" state is the worst, it's uninterruptible sleep, which means something is seriously stuck. The stack trace shows `wait_for_completion`, so there's a completion event involved, that's a clue. Then `i2c_del_adapter` and `dvb_usbv2_probe`. Ah, it's likely related to I2C and a DVB-USB device. And all that's happening in kernel space, naturally.
The reproducer includes `syz_usb_connect` with `0x8086:0x9500`, followed by `i2c` device open, `pipe2`, `openat`, and a `mount` with `9p`. Hmm, the repro steps are a bit all over the place, I'll need to sort that out. Let's see if the sequence is important or if the hang is more directly tied to the DVB-USB probe. The hang is in `i2c_del_adapter` which is called from `dvb_usbv2_probe`. Okay, let's focus on `dvb_usbv2_probe`. I need to figure out what device this is.
It's a USB device. The vendor is Intel (`0x8086`), but what's this `0x9500` product ID? Time to search through the DVB-USB and media/USB drivers for a match on that. I'm going to jump into `dvb_usbv2_probe` to see what's happening.
|
| 7/2 |
2026/05/30 16:39 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c Name:dvb_usbv2_probe]
Results: map[SourceCode: 889: int dvb_usbv2_probe(struct usb_interface *intf,
890: const struct usb_device_id *id)
891: {
892: int ret;
893: struct dvb_usb_device *d;
894: struct usb_device *udev = interface_to_usbdev(intf);
895: struct dvb_usb_driver_info *driver_info =
896: (struct dvb_usb_driver_info *) id->driver_info;
897:
898: dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__,
899: intf->cur_altsetting->desc.bInterfaceNumber);
900:
901: if (!id->driver_info) {
902: dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME);
903: ret = -ENODEV;
904: goto err;
905: }
906:
907: d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL);
908: if (!d) {
909: dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
910: ret = -ENOMEM;
911: goto err;
912: }
913:
914: d->intf = intf;
915: d->name = driver_info->name;
916: d->rc_map = driver_info->rc_map;
917: d->udev = udev;
918: d->props = driver_info->props;
919:
920: if (intf->cur_altsetting->desc.bInterfaceNumber !=
921: d->props->bInterfaceNumber) {
922: ret = -ENODEV;
923: goto err_kfree_d;
924: }
925:
926: mutex_init(&d->usb_mutex);
927: mutex_init(&d->i2c_mutex);
928:
929: if (d->props->size_of_priv) {
930: d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL);
931: if (!d->priv) {
932: dev_err(&d->udev->dev, "%s: kzalloc() failed\n",
933: KBUILD_MODNAME);
934: ret = -ENOMEM;
935: goto err_kfree_d;
936: }
937: }
938:
939: if (d->props->probe) {
940: ret = d->props->probe(d);
941: if (ret)
942: goto err_kfree_priv;
943: }
944:
945: if (d->props->identify_state) {
946: const char *name = NULL;
947: ret = d->props->identify_state(d, &name);
948: if (ret == COLD) {
949: dev_info(&d->udev->dev,
950: "%s: found a '%s' in cold state\n",
951: KBUILD_MODNAME, d->name);
952:
953: if (!name)
954: name = d->props->firmware;
955:
956: ret = dvb_usbv2_download_firmware(d, name);
957: if (ret == 0) {
958: /* device is warm, continue initialization */
959: ;
960: } else if (ret == RECONNECTS_USB) {
961: /*
962: * USB core will call disconnect() and then
963: * probe() as device reconnects itself from the
964: * USB bus. disconnect() will release all driver
965: * resources and probe() is called for 'new'
966: * device. As 'new' device is warm we should
967: * never go here again.
968: */
969: goto exit;
970: } else {
971: goto err_free_all;
972: }
973: } else if (ret != WARM) {
974: goto err_free_all;
975: }
976: }
977:
978: dev_info(&d->udev->dev, "%s: found a '%s' in warm state\n",
979: KBUILD_MODNAME, d->name);
980:
981: ret = dvb_usbv2_init(d);
982: if (ret < 0)
983: goto err_free_all;
984:
985: dev_info(&d->udev->dev,
986: "%s: '%s' successfully initialized and connected\n",
987: KBUILD_MODNAME, d->name);
988: exit:
989: usb_set_intfdata(intf, d);
990:
991: return 0;
992: err_free_all:
993: dvb_usbv2_exit(d);
994: if (d->props->disconnect)
995: d->props->disconnect(d);
996: err_kfree_priv:
997: kfree(d->priv);
998: err_kfree_d:
999: kfree(d);
1000: err:
1001: dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret);
1002: return ret;
1003: }
SourceFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c]
|
| 8/2 |
2026/05/30 16:39 |
llm |
expert |
0m
Model:
Tokens:
input: 21373
output: 54
thoughts: 131
Thoughts: **Analyzing a Suspected DVB-USBv2 Driver Issue**
Okay, so I'm looking at this stack trace and it immediately points to a problem within the I2C subsystem, specifically related to adapter deletion, which is happening during the DVB-USBv2 driver's probe sequence. The trace shows `i2c_del_adapter` being called from within `dvb_usbv2_probe`. The exact line within the DVB-USBv2 code that triggers this deletion is line 993 in `dvb_usb_core.c`. Let's confirm: Line 993 is `dvb_usbv2_exit(d);`. Okay, so it looks like the device is attempting to exit, or clean up after itself when it shouldn't. Something is clearly going wrong during device setup or removal. Now, I need to dig deeper into the `dvb_usbv2_exit` function itself to understand *why* it's being called at this point. I suspect there is a fatal error during the probe, and the device is doing its teardown routine as a result.
|
| 9/2 |
2026/05/30 16:39 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c Name:dvb_usbv2_exit]
Results: map[SourceCode: 837: /* general initialization functions */
838: static int dvb_usbv2_exit(struct dvb_usb_device *d)
839: {
840: dev_dbg(&d->udev->dev, "%s:\n", __func__);
841:
842: dvb_usbv2_remote_exit(d);
843: dvb_usbv2_adapter_exit(d);
844: dvb_usbv2_i2c_exit(d);
845:
846: return 0;
847: }
SourceFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c]
|
| 10/2 |
2026/05/30 16:39 |
llm |
expert |
0m
Model:
Tokens:
input: 22012
output: 58
thoughts: 44
Thoughts: Here's my take on the thought process, as if it were my own:
**Analyzing the DVB-USB v2 I2C Exit Sequence**
Okay, so I'm digging into this `dvb_usbv2_i2c_exit` function. It looks like it's responsible for the cleanup operations when a DVB-USB v2 device is detached or the driver is unloaded. The critical part here is that it's calling `i2c_del_adapter`. That's the function that removes the I2C adapter from the system. Given my understanding of the I2C subsystem, that's precisely what I'd expect. A properly implemented exit function should unregister the hardware interfaces it created. I need to confirm the sequence here, make sure there are no leaks or unexpected side effects. My next step is to carefully examine the context and implementation of this function itself, starting with `dvb_usbv2_i2c_exit`. Let's see how that adapter is being handled.
|
| 11/2 |
2026/05/30 16:39 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c Name:dvb_usbv2_i2c_exit]
Results: map[SourceCode: 81: static int dvb_usbv2_i2c_exit(struct dvb_usb_device *d)
82: {
83: dev_dbg(&d->udev->dev, "%s:\n", __func__);
84:
85: if (d->i2c_adap.algo)
86: i2c_del_adapter(&d->i2c_adap);
87:
88: return 0;
89: }
SourceFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c]
|
| 12/2 |
2026/05/30 16:39 |
llm |
expert |
0m
Model:
Tokens:
input: 22509
output: 46
thoughts: 35
Thoughts: **Investigating the I2C Adapter Deletion Bug**
Okay, the problem is that `i2c_del_adapter` is blocking. That's not supposed to happen, and it's definitely causing issues in our system. The first step is to dive into the source code for `i2c_del_adapter`. I need to figure out *why* it's blocking. What's causing this delay during the adapter deletion process? Are there locks being held inappropriately? Are there pending transactions or resources that aren't being properly handled before the deletion? Time to trace through the function and understand the call flow to pinpoint the source of this blocking behavior.
|
| 13/2 |
2026/05/30 16:39 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_del_adapter]
Results: map[SourceCode:1745: /**
1746: * i2c_del_adapter - unregister I2C adapter
1747: * @adap: the adapter being unregistered
1748: * Context: can sleep
1749: *
1750: * This unregisters an I2C adapter which was previously registered
1751: * by @i2c_add_adapter or @i2c_add_numbered_adapter.
1752: */
1753: void i2c_del_adapter(struct i2c_adapter *adap)
1754: {
1755: struct i2c_adapter *found;
1756: struct i2c_client *client, *next;
1757:
1758: /* First make sure that this adapter was ever added */
1759: mutex_lock(&core_lock);
1760: found = idr_find(&i2c_adapter_idr, adap->nr);
1761: mutex_unlock(&core_lock);
1762: if (found != adap) {
1763: pr_debug("attempting to delete unregistered adapter [%s]\n", adap->name);
1764: return;
1765: }
1766:
1767: i2c_acpi_remove_space_handler(adap);
1768: /* Tell drivers about this removal */
1769: mutex_lock(&core_lock);
1770: bus_for_each_drv(&i2c_bus_type, NULL, adap,
1771: __process_removed_adapter);
1772: mutex_unlock(&core_lock);
1773:
1774: /* Remove devices instantiated from sysfs */
1775: mutex_lock_nested(&adap->userspace_clients_lock,
1776: i2c_adapter_depth(adap));
1777: list_for_each_entry_safe(client, next, &adap->userspace_clients,
1778: detected) {
1779: dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
1780: client->addr);
1781: list_del(&client->detected);
1782: i2c_unregister_device(client);
1783: }
1784: mutex_unlock(&adap->userspace_clients_lock);
1785:
1786: /* Detach any active clients. This can't fail, thus we do not
1787: * check the returned value. This is a two-pass process, because
1788: * we can't remove the dummy devices during the first pass: they
1789: * could have been instantiated by real devices wishing to clean
1790: * them up properly, so we give them a chance to do that first. */
1791: device_for_each_child(&adap->dev, NULL, __unregister_client);
1792: device_for_each_child(&adap->dev, NULL, __unregister_dummy);
1793:
1794: /* device name is gone after device_unregister */
1795: dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);
1796:
1797: pm_runtime_disable(&adap->dev);
1798:
1799: i2c_host_notify_irq_teardown(adap);
1800:
1801: debugfs_remove_recursive(adap->debugfs);
1802:
1803: /* wait until all references to the device are gone
1804: *
1805: * FIXME: This is old code and should ideally be replaced by an
1806: * alternative which results in decoupling the lifetime of the struct
1807: * device from the i2c_adapter, like spi or netdev do. Any solution
1808: * should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
1809: */
1810: init_completion(&adap->dev_released);
1811: device_unregister(&adap->dev);
1812: wait_for_completion(&adap->dev_released);
1813:
1814: /* free bus id */
1815: mutex_lock(&core_lock);
1816: idr_remove(&i2c_adapter_idr, adap->nr);
1817: mutex_unlock(&core_lock);
1818:
1819: /* Clear the device structure in case this adapter is ever going to be
1820: added again */
1821: memset(&adap->dev, 0, sizeof(adap->dev));
1822: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 14/2 |
2026/05/30 16:39 |
llm |
expert |
0m
Model:
Tokens:
input: 24038
output: 48
thoughts: 58
Thoughts: Okay, here's my interpretation of that.
**Diagnosing a Blocked Device Unregistration**
Alright, the `wait_for_completion(&adap->dev_released)` call is hanging. That's immediately pointing the finger at the `device_unregister(&adap->dev)` function, specifically that it *didn't* trigger the expected release function. Now, let me think... given the context, the release function for this I2C adapter device is almost certainly going to be `i2c_adapter_dev_release`. I'll need to figure out why that isn't being invoked, or perhaps, why it's blocked inside that release function. We need to trace where the unregistration is failing to do its job, or where `i2c_adapter_dev_release` is getting stuck.
|
| 15/2 |
2026/05/30 16:39 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter_dev_release]
Results: map[SourceCode:1227: static void i2c_adapter_dev_release(struct device *dev)
1228: {
1229: struct i2c_adapter *adap = to_i2c_adapter(dev);
1230: complete(&adap->dev_released);
1231: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 16/2 |
2026/05/30 16:39 |
llm |
expert |
0m
Model:
Tokens:
input: 24437
output: 43
thoughts: 197
Thoughts: **Debugging an I2C Adapter Release Issue**
Okay, so I'm trying to figure out why `i2c_adapter_dev_release` isn't being called, which is a pretty clear indication of a reference leak. That means something somewhere has grabbed a hold of the i2c device and isn't letting go. I need to trace this back.
The reproducer is key here. It looks like it's a sequence of system calls: `syz_usb_connect`, `syz_open_dev` for an `/dev/i2c-#` device, then `pipe2`, `openat`, and finally a `mount` with `9p` and `trans=fd` with both `rfdno=fd` and `wfdno=fd`. It's that `syz_open_dev` that's immediately grabbing my attention. It opens `/dev/i2c-#`, which is an i2c device node.
And that's it! Opening that device node, *that* is what takes a reference to the i2c adapter. Now I just need to verify that in the code. I need to go check the `i2cdev_open` function in `drivers/i2c/i2c-dev.c` to confirm my suspicion. Let's see if I'm right.
|
| 17/2 |
2026/05/30 16:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_open]
Results: map[SourceCode: 598: static int i2cdev_open(struct inode *inode, struct file *file)
599: {
600: unsigned int minor = iminor(inode);
601: struct i2c_client *client;
602: struct i2c_adapter *adap;
603:
604: adap = i2c_get_adapter(minor);
605: if (!adap)
606: return -ENODEV;
607:
608: /* This creates an anonymous i2c_client, which may later be
609: * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
610: *
611: * This client is ** NEVER REGISTERED ** with the driver model
612: * or I2C core code!! It just holds private copies of addressing
613: * information and maybe a PEC flag.
614: */
615: client = kzalloc(sizeof(*client), GFP_KERNEL);
616: if (!client) {
617: i2c_put_adapter(adap);
618: return -ENOMEM;
619: }
620: snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
621:
622: client->adapter = adap;
623: file->private_data = client;
624:
625: return 0;
626: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 18/2 |
2026/05/30 16:40 |
llm |
expert |
0m
Model:
Tokens:
input: 25436
output: 40
thoughts: 93
Thoughts: Okay, let me break this down.
**Analyzing the I2C Adapter and 9P Filesystem Mount**
Right, so `i2c_get_adapter` – that's a key starting point. It's taking a reference to the adapter device itself. I see that. Now, the reproducer goes on to pass that file descriptor to `mount`, specifically using `9p` and `trans=fd`. Hmmm… wait a second, doesn't the `mount` command, when using `9p` with the `trans=fd` option, *retain* that file descriptor? I need to confirm that. My gut says yes, but I need to be certain. Time to dive into the `net/9p/trans_fd.c` file and specifically look at the `p9_fd_open` function, or something similar, within that section of the driver. I need to understand the lifecycle of that file descriptor within the 9P transport context. Gotta confirm if this mount operation is indeed holding that fd open after the initial call, because that's where the issue likely resides.
|
| 19/2 |
2026/05/30 16:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:net/9p/trans_fd.c Name:p9_fd_open]
Results: map[SourceCode: 830: static int p9_fd_open(struct p9_client *client, int rfd, int wfd)
831: {
832: struct p9_trans_fd *ts = kzalloc(sizeof(struct p9_trans_fd),
833: GFP_KERNEL);
834: if (!ts)
835: return -ENOMEM;
836:
837: ts->rd = fget(rfd);
838: if (!ts->rd)
839: goto out_free_ts;
840: if (!(ts->rd->f_mode & FMODE_READ))
841: goto out_put_rd;
842: /* Prevent workers from hanging on IO when fd is a pipe.
843: * It's technically possible for userspace or concurrent mounts to
844: * modify this flag concurrently, which will likely result in a
845: * broken filesystem. However, just having bad flags here should
846: * not crash the kernel or cause any other sort of bug, so mark this
847: * particular data race as intentional so that tooling (like KCSAN)
848: * can allow it and detect further problems.
849: */
850: data_race(ts->rd->f_flags |= O_NONBLOCK);
851: ts->wr = fget(wfd);
852: if (!ts->wr)
853: goto out_put_rd;
854: if (!(ts->wr->f_mode & FMODE_WRITE))
855: goto out_put_wr;
856: data_race(ts->wr->f_flags |= O_NONBLOCK);
857:
858: client->trans = ts;
859: client->status = Connected;
860:
861: return 0;
862:
863: out_put_wr:
864: fput(ts->wr);
865: out_put_rd:
866: fput(ts->rd);
867: out_free_ts:
868: kfree(ts);
869: return -EIO;
870: }
SourceFile:net/9p/trans_fd.c]
|
| 20/2 |
2026/05/30 16:40 |
llm |
expert |
0m
Model:
Tokens:
input: 26463
output: 54
thoughts: 551
Thoughts: **Analyzing the Reproducer and I2C FD Leak**
Okay, let's break this down. My initial thought is that `p9_fd_open` uses `fget(rfd)` and `fget(wfd)`. The reproducer seems to be passing `rfdno=fd` (where `fd` is the openat file descriptor) and `wfdno=fd` (where `fd` is the pipe read file descriptor), which is interesting.
The reproducer calls `__NR_pipe2`, and then does an `openat` call. It seems to be using the write end of the pipe as `wfd`. Then the reproducer does the mount, constructing the strings with `sprintf`. Here the `openat` fd becomes the `rfdno` and the write-end of the pipe becomes the `wfdno`.
Now, where does the I2C file descriptor come into play? Ah, here it is: `syz_open_dev`, specifically opening the I2C device. But... it doesn't save the returned file descriptor. It just opens it and leaks it! That's a classic setup for a race condition or resource leak.
Since the process doesn't exit immediately, that means the I2C file descriptor remains open. If the file descriptor is open, then an `i2c_get_adapter` reference is held.
And then comes the key: the USB device is disconnected or the probe fails and the probe routine attempts to exit. Wait a second, the `dvb_usbv2_probe` function *does* call `dvb_usbv2_exit` on the error path! This must be the key. Let's go back and carefully analyze `dvb_usbv2_probe`. I need to understand what happens during the exit path.
|
| 21/2 |
2026/05/30 16:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c Name:dvb_usbv2_probe]
Results: map[SourceCode: 889: int dvb_usbv2_probe(struct usb_interface *intf,
890: const struct usb_device_id *id)
891: {
892: int ret;
893: struct dvb_usb_device *d;
894: struct usb_device *udev = interface_to_usbdev(intf);
895: struct dvb_usb_driver_info *driver_info =
896: (struct dvb_usb_driver_info *) id->driver_info;
897:
898: dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__,
899: intf->cur_altsetting->desc.bInterfaceNumber);
900:
901: if (!id->driver_info) {
902: dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME);
903: ret = -ENODEV;
904: goto err;
905: }
906:
907: d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL);
908: if (!d) {
909: dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
910: ret = -ENOMEM;
911: goto err;
912: }
913:
914: d->intf = intf;
915: d->name = driver_info->name;
916: d->rc_map = driver_info->rc_map;
917: d->udev = udev;
918: d->props = driver_info->props;
919:
920: if (intf->cur_altsetting->desc.bInterfaceNumber !=
921: d->props->bInterfaceNumber) {
922: ret = -ENODEV;
923: goto err_kfree_d;
924: }
925:
926: mutex_init(&d->usb_mutex);
927: mutex_init(&d->i2c_mutex);
928:
929: if (d->props->size_of_priv) {
930: d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL);
931: if (!d->priv) {
932: dev_err(&d->udev->dev, "%s: kzalloc() failed\n",
933: KBUILD_MODNAME);
934: ret = -ENOMEM;
935: goto err_kfree_d;
936: }
937: }
938:
939: if (d->props->probe) {
940: ret = d->props->probe(d);
941: if (ret)
942: goto err_kfree_priv;
943: }
944:
945: if (d->props->identify_state) {
946: const char *name = NULL;
947: ret = d->props->identify_state(d, &name);
948: if (ret == COLD) {
949: dev_info(&d->udev->dev,
950: "%s: found a '%s' in cold state\n",
951: KBUILD_MODNAME, d->name);
952:
953: if (!name)
954: name = d->props->firmware;
955:
956: ret = dvb_usbv2_download_firmware(d, name);
957: if (ret == 0) {
958: /* device is warm, continue initialization */
959: ;
960: } else if (ret == RECONNECTS_USB) {
961: /*
962: * USB core will call disconnect() and then
963: * probe() as device reconnects itself from the
964: * USB bus. disconnect() will release all driver
965: * resources and probe() is called for 'new'
966: * device. As 'new' device is warm we should
967: * never go here again.
968: */
969: goto exit;
970: } else {
971: goto err_free_all;
972: }
973: } else if (ret != WARM) {
974: goto err_free_all;
975: }
976: }
977:
978: dev_info(&d->udev->dev, "%s: found a '%s' in warm state\n",
979: KBUILD_MODNAME, d->name);
980:
981: ret = dvb_usbv2_init(d);
982: if (ret < 0)
983: goto err_free_all;
984:
985: dev_info(&d->udev->dev,
986: "%s: '%s' successfully initialized and connected\n",
987: KBUILD_MODNAME, d->name);
988: exit:
989: usb_set_intfdata(intf, d);
990:
991: return 0;
992: err_free_all:
993: dvb_usbv2_exit(d);
994: if (d->props->disconnect)
995: d->props->disconnect(d);
996: err_kfree_priv:
997: kfree(d->priv);
998: err_kfree_d:
999: kfree(d);
1000: err:
1001: dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret);
1002: return ret;
1003: }
SourceFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c]
|
| 22/2 |
2026/05/30 16:40 |
llm |
expert |
1m
Model:
Tokens:
input: 29196
output: 46
thoughts: 572
Thoughts: **Analyzing the I2C Adapter Deletion Bug**
Okay, so here's what's happening. `dvb_usbv2_init` seems to be responsible for registering the i2c adapter. But it can also fail later, leading to the `err_free_all` path. The corresponding cleanup function, `dvb_usbv2_exit`, then calls `i2c_del_adapter`. However, the reproducer manages to open `/dev/i2c-#` *before* the adapter is fully unregistered, specifically after it is registered but before the eventual deletion in the exit path. This is key: it's concurrent execution that's biting us.
The reproducer kicks off with `syz_usb_connect`, which creates the USB device. The kernel then starts probing it, and that's where `dvb_usbv2_probe` calls `dvb_usbv2_init` – registering the I2C adapter. The `init` function then does some other operations, which can be time-consuming or might fail. Crucially, the reproducer opens the `/dev/i2c-#` device *during* this period, while these other operations are happening, effectively establishing a reference to the adapter *before* its cleanup.
Then, `dvb_usbv2_init` fails, triggering the `dvb_usbv2_exit` call from `dvb_usbv2_probe`. This calls `i2c_del_adapter`. This unregisters the device by calling `device_unregister(&adap->dev)`, and *then* waits on `adap->dev_released`. But the reproducer still holds a reference because it opened the `/dev/i2c-#` device before the cleanup! So `wait_for_completion` blocks, expecting the reproducer to drop the reference (close the file descriptor).
The reproducer doesn't close the file descriptor, though. It just hangs around or keeps doing other things. If the reproducer process exits, the fd will be closed, the reference will be dropped, and `wait_for_completion` will finally unblock. But the reproducer might be stuck somewhere, like in `mount` or sleeping. The syzkaller executor could also keep it alive. This explains the "hung task" warning in the bug report: `INFO: task kworker/1:0:24 blocked for more than 143 seconds.`
So, is this a *real* bug? This seems like a race condition, where the i2c adapter removal is blocked because of an open file descriptor. It looks like `i2c_del_adapter` is *supposed* to wait until all references are dropped before actually deleting the adapter. Let me re-examine `i2c_del_adapter` to confirm this behavior.
|
| 23/2 |
2026/05/30 16:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_del_adapter]
Results: map[SourceCode:1745: /**
1746: * i2c_del_adapter - unregister I2C adapter
1747: * @adap: the adapter being unregistered
1748: * Context: can sleep
1749: *
1750: * This unregisters an I2C adapter which was previously registered
1751: * by @i2c_add_adapter or @i2c_add_numbered_adapter.
1752: */
1753: void i2c_del_adapter(struct i2c_adapter *adap)
1754: {
1755: struct i2c_adapter *found;
1756: struct i2c_client *client, *next;
1757:
1758: /* First make sure that this adapter was ever added */
1759: mutex_lock(&core_lock);
1760: found = idr_find(&i2c_adapter_idr, adap->nr);
1761: mutex_unlock(&core_lock);
1762: if (found != adap) {
1763: pr_debug("attempting to delete unregistered adapter [%s]\n", adap->name);
1764: return;
1765: }
1766:
1767: i2c_acpi_remove_space_handler(adap);
1768: /* Tell drivers about this removal */
1769: mutex_lock(&core_lock);
1770: bus_for_each_drv(&i2c_bus_type, NULL, adap,
1771: __process_removed_adapter);
1772: mutex_unlock(&core_lock);
1773:
1774: /* Remove devices instantiated from sysfs */
1775: mutex_lock_nested(&adap->userspace_clients_lock,
1776: i2c_adapter_depth(adap));
1777: list_for_each_entry_safe(client, next, &adap->userspace_clients,
1778: detected) {
1779: dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
1780: client->addr);
1781: list_del(&client->detected);
1782: i2c_unregister_device(client);
1783: }
1784: mutex_unlock(&adap->userspace_clients_lock);
1785:
1786: /* Detach any active clients. This can't fail, thus we do not
1787: * check the returned value. This is a two-pass process, because
1788: * we can't remove the dummy devices during the first pass: they
1789: * could have been instantiated by real devices wishing to clean
1790: * them up properly, so we give them a chance to do that first. */
1791: device_for_each_child(&adap->dev, NULL, __unregister_client);
1792: device_for_each_child(&adap->dev, NULL, __unregister_dummy);
1793:
1794: /* device name is gone after device_unregister */
1795: dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);
1796:
1797: pm_runtime_disable(&adap->dev);
1798:
1799: i2c_host_notify_irq_teardown(adap);
1800:
1801: debugfs_remove_recursive(adap->debugfs);
1802:
1803: /* wait until all references to the device are gone
1804: *
1805: * FIXME: This is old code and should ideally be replaced by an
1806: * alternative which results in decoupling the lifetime of the struct
1807: * device from the i2c_adapter, like spi or netdev do. Any solution
1808: * should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
1809: */
1810: init_completion(&adap->dev_released);
1811: device_unregister(&adap->dev);
1812: wait_for_completion(&adap->dev_released);
1813:
1814: /* free bus id */
1815: mutex_lock(&core_lock);
1816: idr_remove(&i2c_adapter_idr, adap->nr);
1817: mutex_unlock(&core_lock);
1818:
1819: /* Clear the device structure in case this adapter is ever going to be
1820: added again */
1821: memset(&adap->dev, 0, sizeof(adap->dev));
1822: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 24/2 |
2026/05/30 16:41 |
llm |
expert |
0m
Model:
Tokens:
input: 31725
output: 39
thoughts: 608
Thoughts: **Analyzing a Potential I2C Adapter Hang**
Okay, so I'm looking at this `FIXME` comment in the `i2c_del_adapter` function. It seems this code intentionally blocks until *all* references to the I2C adapter device are gone. The comment explicitly calls this old code and suggests a better approach is needed. The core issue is that the lifetime of the `struct device` is tightly coupled to the `i2c_adapter`. It's a design flaw, really, similar to what we see with SPI or network devices. They're suggesting decoupling them, a much better architecture. And they're warning me to *thoroughly* test any solution with `DEBUG_KOBJECT_RELEASE` enabled. Got it.
This blocking behavior in `i2c_del_adapter` is the crux of the problem. If a user process keeps `/dev/i2c-#` open, this function will hang indefinitely. And since `i2c_del_adapter` is called from the USB disconnect or probe error path, that's a problem. This means a hung `i2c_del_adapter` will block the USB hub worker thread (`hub_event`). The consequence? No other USB devices can be enumerated or disconnected on that hub! Classic architectural issue, and it makes sense why it's a `FIXME`.
Now, the important question: who can hold a reference? Can an unprivileged user open `/dev/i2c-#` and cause this? Let's check permissions. Normally, the device nodes are root-owned, group `i2c`, and `crw-rw----`, meaning non-`i2c` users can't touch them. However, what if a process is in a user namespace? User namespaces are tricky with device nodes. They don't magically grant access unless the node is owned by the user in the initial namespace, or they gain access through some other means.
Wait... this is syzkaller we're talking about! The process there runs as root *inside* a user namespace. The syzkaller environment could create those devices with wider permissions, and if syzkaller is running as root then it wouldn't matter. But syzkaller *usually* runs as root, so it will probably have access.
But let's think beyond `/dev/i2c-#`. Is there another way to trigger this without needing a direct `/dev/i2c-#` handle? I bet sysfs interaction is key here. Does opening a sysfs file in `/sys/class/i2c-adapter/i2c-#/` also hold a reference to the underlying `struct device`? We need to look closely at `kernfs` and `sysfs` code. When you open a sysfs file, does it increment the reference count of the underlying `struct device`? It should! Normally `kernfs` will hold a reference to the `kobject`, which in turn holds a reference to the device. Let's verify that quickly.
|
| 25/2 |
2026/05/30 16:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_fop_open]
Results: map[SourceCode: 592: static int kernfs_fop_open(struct inode *inode, struct file *file)
593: {
594: struct kernfs_node *kn = inode->i_private;
595: struct kernfs_root *root = kernfs_root(kn);
596: const struct kernfs_ops *ops;
597: struct kernfs_open_file *of;
598: bool has_read, has_write, has_mmap;
599: int error = -EACCES;
600:
601: if (!kernfs_get_active(kn))
602: return -ENODEV;
603:
604: ops = kernfs_ops(kn);
605:
606: has_read = ops->seq_show || ops->read || ops->mmap;
607: has_write = ops->write || ops->mmap;
608: has_mmap = ops->mmap;
609:
610: /* see the flag definition for details */
611: if (root->flags & KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK) {
612: if ((file->f_mode & FMODE_WRITE) &&
613: (!(inode->i_mode & S_IWUGO) || !has_write))
614: goto err_out;
615:
616: if ((file->f_mode & FMODE_READ) &&
617: (!(inode->i_mode & S_IRUGO) || !has_read))
618: goto err_out;
619: }
620:
621: /* allocate a kernfs_open_file for the file */
622: error = -ENOMEM;
623: of = kzalloc(sizeof(struct kernfs_open_file), GFP_KERNEL);
624: if (!of)
625: goto err_out;
626:
627: /*
628: * The following is done to give a different lockdep key to
629: * @of->mutex for files which implement mmap. This is a rather
630: * crude way to avoid false positive lockdep warning around
631: * mm->mmap_lock - mmap nests @of->mutex under mm->mmap_lock and
632: * reading /sys/block/sda/trace/act_mask grabs sr_mutex, under
633: * which mm->mmap_lock nests, while holding @of->mutex. As each
634: * open file has a separate mutex, it's okay as long as those don't
635: * happen on the same file. At this point, we can't easily give
636: * each file a separate locking class. Let's differentiate on
637: * whether the file has mmap or not for now.
638: *
639: * For similar reasons, writable and readonly files are given different
640: * lockdep key, because the writable file /sys/power/resume may call vfs
641: * lookup helpers for arbitrary paths and readonly files can be read by
642: * overlayfs from vfs helpers when sysfs is a lower layer of overalyfs.
643: *
644: * All three cases look the same. They're supposed to
645: * look that way and give @of->mutex different static lockdep keys.
646: */
647: if (has_mmap)
648: mutex_init(&of->mutex);
649: else if (file->f_mode & FMODE_WRITE)
650: mutex_init(&of->mutex);
651: else
652: mutex_init(&of->mutex);
653:
654: of->kn = kn;
655: of->file = file;
656:
657: /*
658: * Write path needs to atomic_write_len outside active reference.
659: * Cache it in open_file. See kernfs_fop_write_iter() for details.
660: */
661: of->atomic_write_len = ops->atomic_write_len;
662:
663: error = -EINVAL;
664: /*
665: * ->seq_show is incompatible with ->prealloc,
666: * as seq_read does its own allocation.
667: * ->read must be used instead.
668: */
669: if (ops->prealloc && ops->seq_show)
670: goto err_free;
671: if (ops->prealloc) {
672: int len = of->atomic_write_len ?: PAGE_SIZE;
673: of->prealloc_buf = kmalloc(len + 1, GFP_KERNEL);
674: error = -ENOMEM;
675: if (!of->prealloc_buf)
676: goto err_free;
677: mutex_init(&of->prealloc_mutex);
678: }
679:
680: /*
681: * Always instantiate seq_file even if read access doesn't use
682: * seq_file or is not requested. This unifies private data access
683: * and readable regular files are the vast majority anyway.
684: */
685: if (ops->seq_show)
686: error = seq_open(file, &kernfs_seq_ops);
687: else
688: error = seq_open(file, NULL);
689: if (error)
690: goto err_free;
691:
692: of->seq_file = file->private_data;
693: of->seq_file->private = of;
694:
695: /* seq_file clears PWRITE unconditionally, restore it if WRITE */
696: if (file->f_mode & FMODE_WRITE)
697: file->f_mode |= FMODE_PWRITE;
698:
699: /* make sure we have open node struct */
700: error = kernfs_get_open_node(kn, of);
701: if (error)
702: goto err_seq_release;
703:
704: if (ops->open) {
705: /* nobody has access to @of yet, skip @of->mutex */
706: error = ops->open(of);
707: if (error)
708: goto err_put_node;
709: }
710:
711: /* open succeeded, put active references */
712: kernfs_put_active(kn);
713: return 0;
714:
715: err_put_node:
716: kernfs_unlink_open_file(kn, of, true);
717: err_seq_release:
718: seq_release(inode, file);
719: err_free:
720: kfree(of->prealloc_buf);
721: kfree(of);
722: err_out:
723: kernfs_put_active(kn);
724: return error;
725: }
SourceFile:fs/kernfs/file.c]
|
| 26/2 |
2026/05/30 16:41 |
llm |
expert |
0m
Model:
Tokens:
input: 35125
output: 40
thoughts: 92
Thoughts: **Analyzing the Kernfs File Operations**
Okay, let me think this through. So, `kernfs_fop_open` calls `kernfs_get_active(kn)` and then `kernfs_put_active(kn)` at the end. Huh, that's interesting. It's not maintaining the active reference for the entire duration the file is open. That's a different approach than I initially anticipated. But hold on a second... what about `kernfs_get_open_node`? Could *that* be where the kobject reference is managed for an open file? I'd better double-check the implementation of `kernfs_get_open_node`. This deserves a closer look.
|
| 27/2 |
2026/05/30 16:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_get_open_node]
Results: map[SourceCode: 500: /**
501: * kernfs_get_open_node - get or create kernfs_open_node
502: * @kn: target kernfs_node
503: * @of: kernfs_open_file for this instance of open
504: *
505: * If @kn->attr.open exists, increment its reference count; otherwise,
506: * create one. @of is chained to the files list.
507: *
508: * Locking:
509: * Kernel thread context (may sleep).
510: *
511: * Return:
512: * %0 on success, -errno on failure.
513: */
514: static int kernfs_get_open_node(struct kernfs_node *kn,
515: struct kernfs_open_file *of)
516: {
517: struct kernfs_open_node *on;
518: struct mutex *mutex;
519:
520: mutex = kernfs_open_file_mutex_lock(kn);
521: on = kernfs_deref_open_node_locked(kn);
522:
523: if (!on) {
524: /* not there, initialize a new one */
525: on = kzalloc(sizeof(*on), GFP_KERNEL);
526: if (!on) {
527: mutex_unlock(mutex);
528: return -ENOMEM;
529: }
530: atomic_set(&on->event, 1);
531: init_waitqueue_head(&on->poll);
532: INIT_LIST_HEAD(&on->files);
533: rcu_assign_pointer(kn->attr.open, on);
534: }
535:
536: list_add_tail(&of->list, &on->files);
537: if (kn->flags & KERNFS_HAS_RELEASE)
538: on->nr_to_release++;
539:
540: mutex_unlock(mutex);
541: return 0;
542: }
SourceFile:fs/kernfs/file.c]
|
| 28/2 |
2026/05/30 16:41 |
llm |
expert |
1m
Model:
Tokens:
input: 36073
output: 54
thoughts: 589
Thoughts: **My Analysis of the I2C Adapter Unregistration Bug**
Okay, so I've been digging into this, and here's what I've found. It seems the core issue isn't tied to direct kobject references. The problem is around the I2C adapter and how it's handled during unregistration. Opening a sysfs file doesn't prevent device unregistration, that's fine. But, opening `/dev/i2c-#` is a different beast. The `i2cdev_open` function calls `i2c_get_adapter`, which in turn, calls `get_device(&adap->dev)`. And critically, `i2c_del_adapter` waits for `adap->dev` to be released. So, the crux of the issue is that a user process can hold a reference to the i2c adapter by simply having `/dev/i2c-#` open. This causes `i2c_del_adapter` to get stuck indefinitely, blocking forever.
Wait a second… is this a Denial of Service vulnerability? Yeah, it blocks the USB hub worker thread, meaning no other USB devices can be processed. Now, the question is, can an unprivileged user exploit this? `/dev/i2c-#` is typically owned by root:i2c and has permissions 0660. So, a regular user, not in the `i2c` group, can't open it. But what if they *are* in the `i2c` group? We usually assume a "typical" user process that doesn't have special capabilities and restricted access to root-only device nodes. And `/dev/i2c-#` is typically restricted to root and the `i2c` group. So, a regular user, even in the `i2c` group, probably can't trigger it directly.
Okay, so maybe it's not direct. Can an unprivileged user trigger this indirectly through another subsystem, like V4L2, DVB, or input devices? Do any of them hold a reference to the I2C adapter? Let's check `dvb_usbv2_probe`. It looks like it registers the DVB device. If a user opens the DVB device node (like `/dev/dvb/adapter0/frontend0`), does it hold a reference to the I2C adapter? Usually opening a DVB device would hold a reference to the DVB adapter, which in turn, holds a reference to the USB device. Hmm, is `i2c_del_adapter` called *before* the DVB adapter is unregistered? Wait, in `dvb_usbv2_probe`, if `dvb_usbv2_init` fails, it calls `dvb_usbv2_exit`, which calls `i2c_del_adapter`. At that point, the DVB device nodes might not even be registered, or if they are, they get unregistered in `dvb_usbv2_adapter_exit`. Okay, so, I should check `dvb_usbv2_exit`.
|
| 29/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c Name:dvb_usbv2_exit]
Results: map[SourceCode: 837: /* general initialization functions */
838: static int dvb_usbv2_exit(struct dvb_usb_device *d)
839: {
840: dev_dbg(&d->udev->dev, "%s:\n", __func__);
841:
842: dvb_usbv2_remote_exit(d);
843: dvb_usbv2_adapter_exit(d);
844: dvb_usbv2_i2c_exit(d);
845:
846: return 0;
847: }
SourceFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c]
|
| 30/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 37590
output: 44
thoughts: 83
Thoughts: Okay, let me think this through.
**Unraveling DVB Device Unregistration**
Hmm, so the code calls `dvb_usbv2_adapter_exit` *before* `dvb_usbv2_i2c_exit`. That means the DVB devices themselves are being unregistered *first*. That seems logical on the surface... but wait a minute. What if a user application has a DVB device node open? Will the driver handle that gracefully? Specifically, does `dvb_unregister_adapter` block execution until all those open file references are gone? That's a crucial point, I need to verify that. I better go check the implementation of `dvb_unregister_adapter` and see how it handles those scenarios. It can't just unregister and assume everything is closed. There has to be some mechanism to handle potential outstanding references. Gotta dig into the code!
|
| 31/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/dvb-core/dvbdev.c Name:dvb_unregister_adapter]
Results: map[SourceCode: 944: int dvb_unregister_adapter(struct dvb_adapter *adap)
945: {
946: mutex_lock(&dvbdev_register_lock);
947: list_del(&adap->list_head);
948: mutex_unlock(&dvbdev_register_lock);
949: return 0;
950: }
SourceFile:drivers/media/dvb-core/dvbdev.c]
|
| 32/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 38049
output: 44
thoughts: 20
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating a Device Unregistration Issue**
Hmm, so it's not a straightforward blocking situation. That's good to know, narrows down the possibilities. We need to look deeper. The question now becomes: *What* is preventing the complete unregistration? I'm thinking... could it be something related to how the device is unregistered? Is it possibly hanging up at `dvb_unregister_device`? That function is the standard point of removal for DVB devices; it handles a lot of cleanup. I need to refresh my memory on what `dvb_unregister_device` actually *does* under the hood to see if that's where the problem is. Maybe there's a reference count issue, a race condition, or a dangling pointer preventing a clean shutdown. Gotta start there and look at its implementation details. Let's see... I'll pull up the code.
|
| 33/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/dvb-core/dvbdev.c Name:dvb_unregister_device]
Results: map[SourceCode: 635: void dvb_unregister_device(struct dvb_device *dvbdev)
636: {
637: dvb_remove_device(dvbdev);
638: dvb_device_put(dvbdev);
639: }
SourceFile:drivers/media/dvb-core/dvbdev.c]
|
| 34/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 38435
output: 44
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deeply familiar with embedded systems and Linux kernel internals, particularly the DVB (Digital Video Broadcasting) subsystem:
**Diving into `dvb_remove_device`**
Alright, time to dig into `dvb_remove_device`. This is a critical function, no doubt. Understanding the proper cleanup and removal of a DVB device is essential for a stable and predictable system. It's the counterpart to the initialization functions, handling the graceful tear-down when a device is unplugged, removed, or otherwise needs to be de-registered. I need to refresh my memory on what it's responsible for. My experience tells me there are several important aspects to consider: proper resource deallocation, ensuring no dangling pointers remain, and safely unregistering the device from the DVB core so other parts of the kernel don't try to interact with a device that's gone. I also need to be mindful of potential race conditions that can arise when removing a device while other threads are actively using it, which is another area I have to be particularly careful about. Let's see how it's implemented.
|
| 35/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/dvb-core/dvbdev.c Name:dvb_remove_device]
Results: map[SourceCode: 597: void dvb_remove_device(struct dvb_device *dvbdev)
598: {
599: if (!dvbdev)
600: return;
601:
602: down_write(&minor_rwsem);
603: dvb_minors[dvbdev->minor] = NULL;
604: dvb_device_put(dvbdev);
605: up_write(&minor_rwsem);
606:
607: dvb_media_device_free(dvbdev);
608:
609: device_destroy(dvb_class, MKDEV(DVB_MAJOR, dvbdev->minor));
610:
611: list_del(&dvbdev->list_head);
612: }
SourceFile:drivers/media/dvb-core/dvbdev.c]
|
| 36/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 39006
output: 46
thoughts: 194
Thoughts: **Investigating Potential I2C Adapter Unregistration Issues**
Okay, so the current setup doesn't seem to have any built-in blocking mechanisms. If a user opens a DVB device node, it essentially grabs a reference to the `dvbdev` structure. The problem is that `dvb_unregister_device` doesn't block, which has some rather interesting consequences... and these consequences make me a little nervous.
Then there's the `i2c_del_adapter` function, which *does* block. If a user has a file open on `/dev/i2c-#`, they could theoretically stall `i2c_del_adapter` during unregistration, right? However, access to `/dev/i2c-#` is usually restricted.
The question then becomes: Is there another route for maintaining a reference to the I2C adapter? What about situations where other drivers are utilizing the adapter? Specifically, what happens if an I2C client device has been instantiated on the adapter, and a user opens the device node for *that* client?
If that were the case, what could go wrong? Well, `i2c_del_adapter` apparently unregisters *all* I2C clients. That's not good. I'd better take another close look at the `i2c_del_adapter` code to be certain. I need to fully understand all the implications here.
|
| 37/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_del_adapter]
Results: map[SourceCode:1745: /**
1746: * i2c_del_adapter - unregister I2C adapter
1747: * @adap: the adapter being unregistered
1748: * Context: can sleep
1749: *
1750: * This unregisters an I2C adapter which was previously registered
1751: * by @i2c_add_adapter or @i2c_add_numbered_adapter.
1752: */
1753: void i2c_del_adapter(struct i2c_adapter *adap)
1754: {
1755: struct i2c_adapter *found;
1756: struct i2c_client *client, *next;
1757:
1758: /* First make sure that this adapter was ever added */
1759: mutex_lock(&core_lock);
1760: found = idr_find(&i2c_adapter_idr, adap->nr);
1761: mutex_unlock(&core_lock);
1762: if (found != adap) {
1763: pr_debug("attempting to delete unregistered adapter [%s]\n", adap->name);
1764: return;
1765: }
1766:
1767: i2c_acpi_remove_space_handler(adap);
1768: /* Tell drivers about this removal */
1769: mutex_lock(&core_lock);
1770: bus_for_each_drv(&i2c_bus_type, NULL, adap,
1771: __process_removed_adapter);
1772: mutex_unlock(&core_lock);
1773:
1774: /* Remove devices instantiated from sysfs */
1775: mutex_lock_nested(&adap->userspace_clients_lock,
1776: i2c_adapter_depth(adap));
1777: list_for_each_entry_safe(client, next, &adap->userspace_clients,
1778: detected) {
1779: dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
1780: client->addr);
1781: list_del(&client->detected);
1782: i2c_unregister_device(client);
1783: }
1784: mutex_unlock(&adap->userspace_clients_lock);
1785:
1786: /* Detach any active clients. This can't fail, thus we do not
1787: * check the returned value. This is a two-pass process, because
1788: * we can't remove the dummy devices during the first pass: they
1789: * could have been instantiated by real devices wishing to clean
1790: * them up properly, so we give them a chance to do that first. */
1791: device_for_each_child(&adap->dev, NULL, __unregister_client);
1792: device_for_each_child(&adap->dev, NULL, __unregister_dummy);
1793:
1794: /* device name is gone after device_unregister */
1795: dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);
1796:
1797: pm_runtime_disable(&adap->dev);
1798:
1799: i2c_host_notify_irq_teardown(adap);
1800:
1801: debugfs_remove_recursive(adap->debugfs);
1802:
1803: /* wait until all references to the device are gone
1804: *
1805: * FIXME: This is old code and should ideally be replaced by an
1806: * alternative which results in decoupling the lifetime of the struct
1807: * device from the i2c_adapter, like spi or netdev do. Any solution
1808: * should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
1809: */
1810: init_completion(&adap->dev_released);
1811: device_unregister(&adap->dev);
1812: wait_for_completion(&adap->dev_released);
1813:
1814: /* free bus id */
1815: mutex_lock(&core_lock);
1816: idr_remove(&i2c_adapter_idr, adap->nr);
1817: mutex_unlock(&core_lock);
1818:
1819: /* Clear the device structure in case this adapter is ever going to be
1820: added again */
1821: memset(&adap->dev, 0, sizeof(adap->dev));
1822: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 38/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 40855
output: 40
thoughts: 153
Thoughts: **Unraveling Device Deregistration and User Access**
Okay, so I'm thinking through this `device_unregister(&adap->dev)` call. It's supposed to release the device's registration, right? That means it'll drop the reference that's being held. My brain's now buzzing with potential scenarios. If that's the *only* reference, `i2c_adapter_dev_release` gets triggered, which finally sets `adap->dev_released` as done. But, hang on... what if a user process is holding a reference? Then, it's going to block, and we have a potential issue.
Now, the important question is: Can an unprivileged user even *hold* a reference in this context? I'm thinking about the avenues through which they might. Could they do it by opening a sysfs file and somehow maintaining that reference? We did look at `kernfs_fop_open` recently, and I seem to recall that it *doesn't* actually hold a reference to the `kobject`.
My mind is jumping now. What about `mmap`? Does memory mapping a sysfs file create a reference? That's the key question. We need to go check the code, specifically `kernfs_fop_mmap`, to see if that path presents a problem. It seems we need to dive into the details of the sysfs interaction with the device.
|
| 39/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_fop_mmap]
Results: map[SourceCode: 439: static int kernfs_fop_mmap(struct file *file, struct vm_area_struct *vma)
440: {
441: struct kernfs_open_file *of = kernfs_of(file);
442: const struct kernfs_ops *ops;
443: int rc;
444:
445: /*
446: * mmap path and of->mutex are prone to triggering spurious lockdep
447: * warnings and we don't want to add spurious locking dependency
448: * between the two. Check whether mmap is actually implemented
449: * without grabbing @of->mutex by testing HAS_MMAP flag. See the
450: * comment in kernfs_fop_open() for more details.
451: */
452: if (!(of->kn->flags & KERNFS_HAS_MMAP))
453: return -ENODEV;
454:
455: mutex_lock(&of->mutex);
456:
457: rc = -ENODEV;
458: if (!kernfs_get_active(of->kn))
459: goto out_unlock;
460:
461: ops = kernfs_ops(of->kn);
462: rc = ops->mmap(of, vma);
463: if (rc)
464: goto out_put;
465:
466: /*
467: * PowerPC's pci_mmap of legacy_mem uses shmem_zero_setup()
468: * to satisfy versions of X which crash if the mmap fails: that
469: * substitutes a new vm_file, and we don't then want bin_vm_ops.
470: */
471: if (vma->vm_file != file)
472: goto out_put;
473:
474: rc = -EINVAL;
475: if (of->mmapped && of->vm_ops != vma->vm_ops)
476: goto out_put;
477:
478: /*
479: * It is not possible to successfully wrap close.
480: * So error if someone is trying to use close.
481: */
482: if (vma->vm_ops && vma->vm_ops->close)
483: goto out_put;
484:
485: rc = 0;
486: if (!of->mmapped) {
487: of->mmapped = true;
488: of_on(of)->nr_mmapped++;
489: of->vm_ops = vma->vm_ops;
490: }
491: vma->vm_ops = &kernfs_vm_ops;
492: out_put:
493: kernfs_put_active(of->kn);
494: out_unlock:
495: mutex_unlock(&of->mutex);
496:
497: return rc;
498: }
SourceFile:fs/kernfs/file.c]
|
| 40/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 42269
output: 39
thoughts: 40
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Kernel Filesystem Reference Management Deep Dive**
Okay, so I'm digging into this code and I'm seeing it leverages `kernfs_get_active` and `kernfs_put_active`. That's standard practice, makes sense for reference counting and ensuring the kernel filesystem structures don't disappear out from under me during critical operations. Good, good. But then… hold on a second. My Spidey-sense is tingling. Where's `kernfs_vm_ops` fitting into all this? Is it involved in reference counting? Are we potentially holding a reference to something through the `kernfs_vm_ops` structure that we aren't properly managing with these `get/put` calls? I need to clarify that. This is where things can get tricky with concurrent access to kernel data. I need to be *absolutely* sure that the lifetime management is solid here; I don't want any use-after-free scenarios lurking around. I'll need to trace the usage of that pointer and see what's going on underneath.
|
| 41/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_vma_open]
Results: map[SourceCode: 352: static void kernfs_vma_open(struct vm_area_struct *vma)
353: {
354: struct file *file = vma->vm_file;
355: struct kernfs_open_file *of = kernfs_of(file);
356:
357: if (!of->vm_ops)
358: return;
359:
360: if (!kernfs_get_active(of->kn))
361: return;
362:
363: if (of->vm_ops->open)
364: of->vm_ops->open(vma);
365:
366: kernfs_put_active(of->kn);
367: }
SourceFile:fs/kernfs/file.c]
|
| 42/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 42840
output: 35
thoughts: 63
Thoughts: **Investigating Sysfs and Device Node Lifecycle**
Okay, so I'm digging into how sysfs handles kobjects and it's looking like it *doesn't* maintain a reference to the kobject for the entire duration of a file being open. That's... interesting, and potentially a source of subtle race conditions, depending on the implementation details, of course.
Now, that brings up a good question: what happens if a user-space process opens a device node? I need to understand the lifecycle there to fully grasp the implications of sysfs' behavior.
Right, when a user opens a device node, `chrdev_open` gets called, that's the crucial entry point. Time to dive into the `chrdev_open` function and see how it handles things, especially the interaction with the underlying device and its kobject. Let's see how this all connects.
|
| 43/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/char_dev.c Name:chrdev_open]
Results: map[SourceCode: 370: /*
371: * Called every time a character special file is opened
372: */
373: static int chrdev_open(struct inode *inode, struct file *filp)
374: {
375: const struct file_operations *fops;
376: struct cdev *p;
377: struct cdev *new = NULL;
378: int ret = 0;
379:
380: spin_lock(&cdev_lock);
381: p = inode->i_cdev;
382: if (!p) {
383: struct kobject *kobj;
384: int idx;
385: spin_unlock(&cdev_lock);
386: kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
387: if (!kobj)
388: return -ENXIO;
389: new = container_of(kobj, struct cdev, kobj);
390: spin_lock(&cdev_lock);
391: /* Check i_cdev again in case somebody beat us to it while
392: we dropped the lock. */
393: p = inode->i_cdev;
394: if (!p) {
395: inode->i_cdev = p = new;
396: list_add(&inode->i_devices, &p->list);
397: new = NULL;
398: } else if (!cdev_get(p))
399: ret = -ENXIO;
400: } else if (!cdev_get(p))
401: ret = -ENXIO;
402: spin_unlock(&cdev_lock);
403: cdev_put(new);
404: if (ret)
405: return ret;
406:
407: ret = -ENXIO;
408: fops = fops_get(p->ops);
409: if (!fops)
410: goto out_cdev_put;
411:
412: replace_fops(filp, fops);
413: if (filp->f_op->open) {
414: ret = filp->f_op->open(inode, filp);
415: if (ret)
416: goto out_cdev_put;
417: }
418:
419: return 0;
420:
421: out_cdev_put:
422: cdev_put(p);
423: return ret;
424: }
SourceFile:fs/char_dev.c]
|
| 44/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 43933
output: 45
thoughts: 82
Thoughts: **Delving into Character Device Lifetime**
Okay, so I'm tracing the lifecycle of these character devices. It looks like `chrdev_open` kicks things off, and that calls `cdev_get(p)`. My concern here is the lifetime management of that `cdev` kobject. The documentation is clear that `cdev` can either be embedded within a `struct device` or allocated independently. That’s what I'm focused on understanding because if it is handled differently in different drivers then that could lead to memory errors.
With most drivers, `cdev` is embedded. I'm trying to see if there's a specific pattern for the allocation. So, I need to know where I can see the rules for the allocation. For the `i2c-dev` driver, I'm thinking the `cdev` might actually be allocated separately. If that is the case, I need to see how that allocation works to know if the ref-counting is being correctly handled.
Let's check `i2cdev_attach_adapter`. That should reveal how that particular driver is handling the `cdev` object, and whether it's embedded or allocated separately, as well as the important ref-counting. I need to get to the bottom of this.
|
| 45/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_attach_adapter]
Results: map[SourceCode: 664: static int i2cdev_attach_adapter(struct device *dev)
665: {
666: struct i2c_adapter *adap;
667: struct i2c_dev *i2c_dev;
668: int res;
669:
670: if (dev->type != &i2c_adapter_type)
671: return NOTIFY_DONE;
672: adap = to_i2c_adapter(dev);
673:
674: i2c_dev = get_free_i2c_dev(adap);
675: if (IS_ERR(i2c_dev))
676: return NOTIFY_DONE;
677:
678: cdev_init(&i2c_dev->cdev, &i2cdev_fops);
679: i2c_dev->cdev.owner = THIS_MODULE;
680:
681: device_initialize(&i2c_dev->dev);
682: i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr);
683: i2c_dev->dev.class = &i2c_dev_class;
684: i2c_dev->dev.parent = &adap->dev;
685: i2c_dev->dev.release = i2cdev_dev_release;
686:
687: res = dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);
688: if (res)
689: goto err_put_i2c_dev;
690:
691: res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);
692: if (res)
693: goto err_put_i2c_dev;
694:
695: pr_debug("adapter [%s] registered as minor %d\n", adap->name, adap->nr);
696: return NOTIFY_OK;
697:
698: err_put_i2c_dev:
699: put_i2c_dev(i2c_dev, false);
700: return NOTIFY_DONE;
701: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 46/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 45019
output: 45
thoughts: 153
Thoughts: **Analyzing the I2C Adapter Removal Process**
Okay, so I'm tracing this `i2c_dev` structure and how it relates to the adapter's lifecycle. Right now, it looks like `i2c_dev->dev.parent` is being set to the adapter's device, specifically `adap->dev`. This makes sense; when `i2c_dev->dev` gets registered, it's essentially taking a reference to its parent, the adapter. Standard parent-child relationship in the device model.
Now, the crucial point: when `i2c_del_adapter` gets called, it unregisters the adapter. But how does that affect the devices attached to it? I need to understand the complete sequence. Wait a second... `i2c_del_adapter` calls `bus_for_each_drv` with `&i2c_bus_type`, passing `adap` and the `__process_removed_adapter` function. That seems interesting. Does this mechanism notify the `i2c-dev` driver about the adapter being removed?
Ah, right! I remember now. The `i2c-dev` driver registers a notifier on the i2c bus, which means it should be getting notified about this removal. Let's delve into `i2cdev_notifier_call` to confirm and see how it handles the adapter going away. It needs to clean up its references, unregister, or otherwise handle the event. This is where the magic happens!
|
| 47/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_notifier_call]
Results: map[SourceCode: 722: static int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action,
723: void *data)
724: {
725: struct device *dev = data;
726:
727: switch (action) {
728: case BUS_NOTIFY_ADD_DEVICE:
729: return i2cdev_attach_adapter(dev);
730: case BUS_NOTIFY_DEL_DEVICE:
731: return i2cdev_detach_adapter(dev);
732: }
733:
734: return NOTIFY_DONE;
735: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 48/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 45763
output: 45
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Investigating `i2cdev_detach_adapter`**
Okay, let's dive into this `i2cdev_detach_adapter` function. It's a critical piece, likely handling the cleanup and disconnection of I2C adapter resources. I'm immediately thinking about the potential race conditions here. Detaching an adapter implies that devices using it will also need to be handled, or we'll have dangling pointers and resource leaks galore. I need to understand the sequencing and synchronization involved. Are there mutexes or other locking mechanisms to protect shared data structures?
My mind is already running through the possible scenarios: device removal, driver unbinding, or system shutdown. The function needs to handle each case gracefully, ensuring that all devices attached to the adapter are either properly released or, in some cases, re-parented to a new adapter if appropriate. I’m concerned about error handling; what happens if a device fails to detach cleanly? Does it block the entire process, or is there a timeout or some form of graceful degradation?
I need to understand what resources are being detached: file descriptors, device nodes, interrupt handlers, I/O registers, the works. Is the memory associated with the adapter being freed immediately, or is there a delayed garbage collection strategy? I’ll also check how this function integrates with the overall I2C subsystem architecture. Are there callbacks or hooks that this function triggers within the kernel?
Finally, let's keep an eye out for potential security vulnerabilities. Improper handling of device access could lead to privilege escalation or information leakage. This needs to be scrutinized thoroughly. I'm going to start by looking for calls to this function and see where it's called from and how. This will help determine the context of operations and reveal possible corner cases.
|
| 49/2 |
2026/05/30 16:43 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_detach_adapter]
Results: map[SourceCode: 703: static int i2cdev_detach_adapter(struct device *dev)
704: {
705: struct i2c_adapter *adap;
706: struct i2c_dev *i2c_dev;
707:
708: if (dev->type != &i2c_adapter_type)
709: return NOTIFY_DONE;
710: adap = to_i2c_adapter(dev);
711:
712: i2c_dev = i2c_dev_get_by_minor(adap->nr);
713: if (!i2c_dev) /* attach_adapter must have failed */
714: return NOTIFY_DONE;
715:
716: put_i2c_dev(i2c_dev, true);
717:
718: pr_debug("adapter [%s] unregistered\n", adap->name);
719: return NOTIFY_OK;
720: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 50/2 |
2026/05/30 16:43 |
llm |
expert |
0m
Model:
Tokens:
input: 46533
output: 44
thoughts: 37
Thoughts: Here's my thought process, as if I were considering this code:
**Analyzing the I2C Device Removal**
Okay, hold on a second. That `put_i2c_dev(i2c_dev, true)` call... that's concerning. The `true` argument is throwing up a red flag. Typically, `put_i2c_dev` is used to decrement a device's reference count. But the `true` likely indicates an immediate release, which in this context means removal. It's removing the device altogether!
Given the critical nature of I2C device management and the potential for a race condition, I should probably double-check the implementation of `put_i2c_dev` right now, especially how it handles that boolean flag. I need to understand exactly what happens when the device is "put" with this specific argument. The implications are significant, so I need to be absolutely sure what's going on under the hood before proceeding. Let's delve into the source code for that function immediately.
|
| 51/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:put_i2c_dev]
Results: map[SourceCode: 88: static void put_i2c_dev(struct i2c_dev *i2c_dev, bool del_cdev)
89: {
90: spin_lock(&i2c_dev_list_lock);
91: list_del(&i2c_dev->list);
92: spin_unlock(&i2c_dev_list_lock);
93: if (del_cdev)
94: cdev_device_del(&i2c_dev->cdev, &i2c_dev->dev);
95: put_device(&i2c_dev->dev);
96: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 52/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 47041
output: 34
thoughts: 321
Thoughts: **Analyzing Device Lifetime with `cdev_device_del`**
Okay, so I'm thinking about how `cdev_device_del` handles removing a character device and its associated device. The initial assumption, naturally, is that it just does that. Simple enough. But wait a minute... I need to consider that `chrdev_open` might be holding a reference to the `cdev` while the file is open. That would be problematic, right?
Alright, let's step through `chrdev_open`. Ah, it uses `cdev_get(p)`! Good, that increments the reference count. And then, at the *end*, there's a corresponding `cdev_put(p)`. Okay, that *seems* to take care of it, making sure the ref count goes back down when the file is done. I'm going to double check this though, just to make sure I don't miss anything subtle.
Hmm, wait a second. Let's look at `chrdev_open` one more time. The code has a `goto out_cdev_put;` statement if there's an error. Oh, but the critical part of `chrdev_open` is the `f_op->open` function call. If that fails... then `cdev_put` *is* called. But what if the call *succeeds*? Let me look at the code block again:
```c
replace_fops(filp, fops);
if (filp->f_op->open) {
ret = filp->f_op->open(inode, filp);
if (ret)
goto out_cdev_put;
}
return 0;
```
Ah, that's it! If it's successful, `cdev_put(p)` is *not* called in `chrdev_open`. That's where things get interesting. So, the `cdev_put(p)` call must happen elsewhere. Now I see it. It must be called in `__fput` when the file is closed. I need to make sure this is correct. Let's dig into `fs/file_table.c` to confirm that `__fput` is indeed the place where `cdev_put` gets called. Yes, that is correct, so when a file is closed, the ref count is decremented. Okay, I'm confident that the reference counting is handled correctly now.
|
| 53/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/file_table.c Name:__fput]
Results: map[SourceCode: 437: /* the real guts of fput() - releasing the last reference to file
438: */
439: static void __fput(struct file *file)
440: {
441: struct dentry *dentry = file->f_path.dentry;
442: struct vfsmount *mnt = file->f_path.mnt;
443: struct inode *inode = file->f_inode;
444: fmode_t mode = file->f_mode;
445:
446: if (unlikely(!(file->f_mode & FMODE_OPENED)))
447: goto out;
448:
449: might_sleep();
450:
451: fsnotify_close(file);
452: /*
453: * The function eventpoll_release() should be the first called
454: * in the file cleanup chain.
455: */
456: eventpoll_release(file);
457: locks_remove_file(file);
458:
459: security_file_release(file);
460: if (unlikely(file->f_flags & FASYNC)) {
461: if (file->f_op->fasync)
462: file->f_op->fasync(-1, file, 0);
463: }
464: if (file->f_op->release)
465: file->f_op->release(inode, file);
466: if (unlikely(S_ISCHR(inode->i_mode) && inode->i_cdev != NULL &&
467: !(mode & FMODE_PATH))) {
468: cdev_put(inode->i_cdev);
469: }
470: fops_put(file->f_op);
471: file_f_owner_release(file);
472: put_file_access(file);
473: dput(dentry);
474: if (unlikely(mode & FMODE_NEED_UNMOUNT))
475: dissolve_on_fput(mnt);
476: mntput(mnt);
477: out:
478: file_free(file);
479: }
SourceFile:fs/file_table.c]
|
| 54/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 48618
output: 43
thoughts: 152
Thoughts: **My Analysis of the `cdev` Lifecycle in the I2Cdev Driver**
Okay, so I'm digging into this `cdev` management within the I2Cdev driver. The initial assumption, and a pretty reasonable one at that, is that `cdev_put(inode->i_cdev);` is indeed called within `__fput`. That makes sense: as long as the file representing the I2C device is open, the `cdev` structure has a reference. Naturally, the `cdev` itself is embedded within the `i2c_dev` structure, and it looks like `i2c_dev` gets freed when `i2c_dev->dev` is released.
Now, a potential gotcha I'm seeing is that `cdev_device_del` calls `cdev_del`, which is good because it *removes* the `cdev` from the system. Crucially though, `cdev_del` *doesn't* free the memory if there are still outstanding references to the `cdev`. This is a vital point to keep in mind, and it's something I'll need to check later for potential memory leaks.
But wait, I need to backtrack and make sure I haven't missed anything. I'm recalling now that `i2cdev_open` also, quite explicitly, calls `i2c_get_adapter(minor)`. That could very well play a role in the reference counting, or there might be some interplay between the two. Best check `i2cdev_open` again and make sure I have all the pieces of the puzzle.
|
| 55/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_open]
Results: map[SourceCode: 598: static int i2cdev_open(struct inode *inode, struct file *file)
599: {
600: unsigned int minor = iminor(inode);
601: struct i2c_client *client;
602: struct i2c_adapter *adap;
603:
604: adap = i2c_get_adapter(minor);
605: if (!adap)
606: return -ENODEV;
607:
608: /* This creates an anonymous i2c_client, which may later be
609: * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
610: *
611: * This client is ** NEVER REGISTERED ** with the driver model
612: * or I2C core code!! It just holds private copies of addressing
613: * information and maybe a PEC flag.
614: */
615: client = kzalloc(sizeof(*client), GFP_KERNEL);
616: if (!client) {
617: i2c_put_adapter(adap);
618: return -ENOMEM;
619: }
620: snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
621:
622: client->adapter = adap;
623: file->private_data = client;
624:
625: return 0;
626: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 56/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 49631
output: 43
thoughts: 40
Thoughts: Here's my understanding, as if I were thinking it through:
**Analyzing I2C Adapter Management**
Okay, so I'm looking at how I2C adapters are handled in the kernel. I see `i2c_get_adapter` takes a pointer to `adap->dev`. That makes sense; it's getting a reference to the device's adapter structure. And I need to track this reference for proper handling. Now, I have to consider how this reference is released. Ah, here it is: `i2cdev_release` calls `i2c_put_adapter`. That completes the cycle. `i2c_put_adapter` should then decrement the reference count, releasing the adapter when the count reaches zero. I need to make sure I understand the interaction between getting the adapter and releasing it correctly, since it's going to be fundamental to resource management.
|
| 57/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_release]
Results: map[SourceCode: 628: static int i2cdev_release(struct inode *inode, struct file *file)
629: {
630: struct i2c_client *client = file->private_data;
631:
632: i2c_put_adapter(client->adapter);
633: kfree(client);
634: file->private_data = NULL;
635:
636: return 0;
637: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 58/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 50074
output: 43
thoughts: 244
Thoughts: **My Analysis of a Potential Denial of Service Vulnerability**
Okay, here's how I'm thinking through this issue. The core problem is this: as long as a file associated with an I2C adapter (`adap->dev`) is open, there's an active reference. The `i2c_del_adapter` function, when called to remove an I2C adapter, deliberately waits for that reference (`adap->dev`) to be released. This is by design, of course. So, if a process has `/dev/i2c-#` open and a call is made to `i2c_del_adapter`, the removal will block, right?
In the reported scenario, this blocking occurs within the `kworker/1:0` thread, specifically while executing the `hub_event` function. And that thread is the USB hub worker thread! So if that thread is blocked, the USB hub can't process further events. We're talking about new devices being plugged in, or devices being unplugged, which is vital functionality. That's a Denial of Service, pure and simple.
Now, the important question is: can this be triggered by a normal, unprivileged process? To cause this, a process needs to open a device file like `/dev/i2c-#`. But I know that by default, these files are created with permissions like `0600` or `0660` and are owned by `root:root` or `root:i2c`. So, if I'm understanding this correctly, the average user won't be able to open the file to cause the deadlock.
I need to quickly double-check the permissions on the `i2c_dev_class` in `drivers/i2c/i2c-dev.c` to be sure I'm not missing anything. I need to be absolutely sure this isn't an issue.
|
| 59/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_init]
Results: map[SourceCode: SourceFile:]
|
| 60/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 50790
output: 44
thoughts: 13
Thoughts: Okay, let's dive into this. Here's my thought process on this:
**Analyzing the Initialization of the I2C Device**
Alright, my immediate focus is on `i2c_dev_init`. I need to understand how the I2C device is being initialized. This is crucial for establishing the foundation for all subsequent I2C operations. I want to know exactly what steps are being taken, what resources are being allocated, and how the device driver is being set up. I'll be looking for potential areas of concern that might lead to later problems in communication. Memory allocation, interrupt setup, register configurations – all of these are key aspects I'll be examining. Getting the initialization right at this stage is absolutely critical, otherwise I'm basically setting myself up for failure down the line. Let's see what's happening within this function...
|
| 61/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2c_dev_init]
Results: map[SourceCode: 759: static int __init i2c_dev_init(void)
760: {
761: int res;
762:
763: pr_info("i2c /dev entries driver\n");
764:
765: res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
766: if (res)
767: goto out;
768:
769: res = class_register(&i2c_dev_class);
770: if (res)
771: goto out_unreg_chrdev;
772:
773: /* Keep track of adapters which will be added or removed later */
774: res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
775: if (res)
776: goto out_unreg_class;
777:
778: /* Bind to already existing adapters right away */
779: i2c_for_each_dev(NULL, i2c_dev_attach_adapter);
780:
781: return 0;
782:
783: out_unreg_class:
784: class_unregister(&i2c_dev_class);
785: out_unreg_chrdev:
786: unregister_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS);
787: out:
788: pr_err("Driver Initialisation failed\n");
789: return res;
790: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 62/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 51541
output: 44
thoughts: 19
Thoughts: Here's my take on those thoughts, tailored for an expert audience:
**Analyzing the `i2c_dev_class`**
Okay, so we're dealing with the `i2c_dev_class`. My initial instinct is to dive straight into its definition. I need to understand its structure, its parent classes (if any), and any member functions or data members that might be relevant to the current problem. I'm looking for the core functionality this class provides, focusing on how it encapsulates and manages I2C device interactions within the system. Any specific data structures or methods pertaining to address handling, bus arbitration, or error management will be of immediate interest. Let's see what we can dig up.
|
| 63/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2c_dev_class]
Results: map[SourceCode: 651: static const struct class i2c_dev_class = {
652: .name = "i2c-dev",
653: .dev_groups = i2c_groups,
654: };
SourceFile:drivers/i2c/i2c-dev.c]
|
| 64/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 51854
output: 48
thoughts: 191
Thoughts: **Analyzing Device Node Permissions**
Okay, so I'm looking at how permissions are handled and it seems there's no explicit callback defined to alter permissions for this specific `devnode`. The implication is that the defaults will be `0600` for permissions and the owner will be `root:root`. That's standard behavior.
I'm thinking about typical Udev rules that are deployed in userspace and, in general, I'm used to seeing the group set to `i2c` and permissions set to `0660`. Even if we make this change, an unprivileged user, and one who isn't a member of the `i2c` group, still won't be able to access the `/dev/i2c-#` device. Makes sense, that's what we want. It's not accessible from a non-privileged process directly.
But, hold on a minute... What if a user *could* somehow trigger this process through a *different* device node? Something like a DVB device node? We need to look for any potential bypasses. I've already checked `dvb_unregister_device` and it doesn't appear to be blocking in any way. What about a V4L2 device node? Let's check `video_unregister_device` in `drivers/media/v4l2-core/v4l2-dev.c` to see if there's any way a user could gain access through a back channel.
|
| 65/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/v4l2-core/v4l2-dev.c Name:video_unregister_device]
Results: map[SourceCode:1098: /**
1099: * video_unregister_device - unregister a video4linux device
1100: * @vdev: the device to unregister
1101: *
1102: * This unregisters the passed device. Future open calls will
1103: * be met with errors.
1104: */
1105: void video_unregister_device(struct video_device *vdev)
1106: {
1107: /* Check if vdev was ever registered at all */
1108: if (!vdev || !video_is_registered(vdev))
1109: return;
1110:
1111: mutex_lock(&videodev_lock);
1112: /* This must be in a critical section to prevent a race with v4l2_open.
1113: * Once this bit has been cleared video_get may never be called again.
1114: */
1115: clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
1116: mutex_unlock(&videodev_lock);
1117: if (test_bit(V4L2_FL_USES_V4L2_FH, &vdev->flags))
1118: v4l2_event_wake_all(vdev);
1119: device_unregister(&vdev->dev);
1120: }
SourceFile:drivers/media/v4l2-core/v4l2-dev.c]
|
| 66/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 52839
output: 36
thoughts: 40
Thoughts: Okay, here's my take on those thoughts, framed as an expert's inner monologue:
**Unregistering Devices: A Quick Mental Check**
Right, so we're talking about unregistering a virtual device. The code calls `device_unregister(&vdev->dev)`. That's standard practice, and importantly, it's non-blocking. Good, that's what I'd expect. It's designed to be efficient. But my mind immediately jumps to other device types... specifically, what about the input side of things? We're often dealing with more than just block devices or network devices. I need to quickly ensure we handle input devices correctly, too.
Let's quickly verify how the input devices are unregistered. I better check the `input_unregister_device` function to ensure that all relevant resources are properly released, and that it's handling the unregistering process safely. Gotta make sure we cover all our bases and that we're following the correct shutdown path for these input devices.
|
| 67/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/input.c Name:input_unregister_device]
Results: map[SourceCode:2432: /**
2433: * input_unregister_device - unregister previously registered device
2434: * @dev: device to be unregistered
2435: *
2436: * This function unregisters an input device. Once device is unregistered
2437: * the caller should not try to access it as it may get freed at any moment.
2438: */
2439: void input_unregister_device(struct input_dev *dev)
2440: {
2441: if (dev->devres_managed) {
2442: WARN_ON(devres_destroy(dev->dev.parent,
2443: devm_input_device_unregister,
2444: devm_input_device_match,
2445: dev));
2446: __input_unregister_device(dev);
2447: /*
2448: * We do not do input_put_device() here because it will be done
2449: * when 2nd devres fires up.
2450: */
2451: } else {
2452: __input_unregister_device(dev);
2453: input_put_device(dev);
2454: }
2455: }
SourceFile:drivers/input/input.c]
|
| 68/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 53504
output: 37
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my thought process on checking `__input_unregister_device`:
**Examining `__input_unregister_device`**
Alright, I'm thinking, let's get right down to it. I need to understand what's happening with `__input_unregister_device`. This is a core function, so I'm expecting it to be responsible for the fundamental de-registration of input devices within the kernel. I'm imagining it deals with more than just simple cleanup. This means memory deallocation, freeing up resources, potentially signalling a disconnect to the relevant drivers or subsystems. I anticipate it likely interacts with the input device's internal data structures, updating flags, and handling any dependencies on other kernel components.
I'll be looking closely to see how it handles race conditions or concurrency issues. Is there any locking involved to protect against concurrent unregistration attempts or accesses to the device while it's being unregistered? I'll be particularly interested in whether it handles graceful shutdown and cleanup to prevent any crashes or unexpected behavior. It's likely involved in the process of the removal of the device from the input system and thus will be handling cleanups, freeing resources and informing other parts of the kernel about this change.
|
| 69/2 |
2026/05/30 16:44 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/input.c Name:__input_unregister_device]
Results: map[SourceCode:2215: static void __input_unregister_device(struct input_dev *dev)
2216: {
2217: struct input_handle *handle, *next;
2218:
2219: input_disconnect_device(dev);
2220:
2221: scoped_guard(mutex, &input_mutex) {
2222: list_for_each_entry_safe(handle, next, &dev->h_list, d_node)
2223: handle->handler->disconnect(handle);
2224: WARN_ON(!list_empty(&dev->h_list));
2225:
2226: timer_delete_sync(&dev->timer);
2227: list_del_init(&dev->node);
2228:
2229: input_wakeup_procfs_readers();
2230: }
2231:
2232: device_del(&dev->dev);
2233: }
SourceFile:drivers/input/input.c]
|
| 70/2 |
2026/05/30 16:44 |
llm |
expert |
0m
Model:
Tokens:
input: 54114
output: 36
thoughts: 334
Thoughts: **My Analysis of Device Removal in the I2C Subsystem**
Okay, so I'm thinking about how devices are removed, specifically in relation to the I2C subsystem and potential blocking behavior. The `device_del` function is called, which is a key signal. Crucially, it doesn't seem to block. This is a vital first observation. So, where does the blocking come in? It seems `i2c_del_adapter` is the intentional blocker, waiting for references to be dropped.
To trigger this blocking, a user needs to hold a reference to the I2C adapter device. The standard way to do this from userspace is through opening `/dev/i2c-#` devices. Opening sysfs files, as we've already seen in `kernfs_fop_open`, doesn't create a persistent reference.
But I need to consider all the possibilities. What if a user opens a sysfs file *and then* calls `mmap`? Let's refresh our memory. `kernfs_fop_mmap` doesn't hold a reference either. Okay, what about `ioctl` on sysfs? Sysfs doesn't even support `ioctl`.
Now, what about `read` or `write` operations on sysfs files? Ah, these are interesting. They call `kernfs_get_active` and `kernfs_put_active` for the *duration* of the read/write operation. If a user *could* block inside a `read` or `write`, they might be able to hold that active reference.
However, `kernfs_get_active` only holds an active reference to the `kernfs_node`, not directly to the underlying kobject. But wait a moment...`device_del` calls `kobject_del`, which leads to `sysfs_remove_dir`. That in turn calls `kernfs_remove`, and that function *waits* for all active references to drain! This is the key. It looks like `kernfs_remove` is doing the blocking. I need to take a closer look at `kernfs_remove` itself.
|
| 71/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/dir.c Name:kernfs_remove]
Results: map[SourceCode:1532: /**
1533: * kernfs_remove - remove a kernfs_node recursively
1534: * @kn: the kernfs_node to remove
1535: *
1536: * Remove @kn along with all its subdirectories and files.
1537: */
1538: void kernfs_remove(struct kernfs_node *kn)
1539: {
1540: struct kernfs_root *root;
1541:
1542: if (!kn)
1543: return;
1544:
1545: root = kernfs_root(kn);
1546:
1547: down_write(&root->kernfs_rwsem);
1548: __kernfs_remove(kn);
1549: up_write(&root->kernfs_rwsem);
1550: }
SourceFile:fs/kernfs/dir.c]
|
| 72/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 55215
output: 37
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing the thought process, delivered in the first person, assuming the user is an expert.
**Diving into `__kernfs_remove`**
Alright, time to dig into `__kernfs_remove`. I need to refresh my understanding of this function. Let's see what's happening internally – what’s the removal strategy here? Is it a graceful unmount, or more of a direct deletion? I'm particularly interested in how it handles potential race conditions, concurrent access, and any locking mechanisms employed within kernfs. I want to see if it efficiently manages any associated metadata and resources. I should pay close attention to any error handling too. Knowing `__kernfs_remove`'s details is crucial for understanding the wider file system interaction and behavior, especially in complex, concurrent operations. I'll focus on the data structures it manipulates and the control flow involved to ensure there are no surprises. Let's get to it.
|
| 73/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/dir.c Name:__kernfs_remove]
Results: map[SourceCode:1467: static void __kernfs_remove(struct kernfs_node *kn)
1468: {
1469: struct kernfs_node *pos, *parent;
1470:
1471: /* Short-circuit if non-root @kn has already finished removal. */
1472: if (!kn)
1473: return;
1474:
1475: lockdep_assert_held_write(&kernfs_root(kn)->kernfs_rwsem);
1476:
1477: /*
1478: * This is for kernfs_remove_self() which plays with active ref
1479: * after removal.
1480: */
1481: if (kernfs_parent(kn) && RB_EMPTY_NODE(&kn->rb))
1482: return;
1483:
1484: pr_debug("kernfs %s: removing\n", kernfs_rcu_name(kn));
1485:
1486: /* prevent new usage by marking all nodes removing and deactivating */
1487: pos = NULL;
1488: while ((pos = kernfs_next_descendant_post(pos, kn))) {
1489: pos->flags |= KERNFS_REMOVING;
1490: if (kernfs_active(pos))
1491: atomic_add(KN_DEACTIVATED_BIAS, &pos->active);
1492: }
1493:
1494: /* deactivate and unlink the subtree node-by-node */
1495: do {
1496: pos = kernfs_leftmost_descendant(kn);
1497:
1498: /*
1499: * kernfs_drain() may drop kernfs_rwsem temporarily and @pos's
1500: * base ref could have been put by someone else by the time
1501: * the function returns. Make sure it doesn't go away
1502: * underneath us.
1503: */
1504: kernfs_get(pos);
1505:
1506: kernfs_drain(pos);
1507: parent = kernfs_parent(pos);
1508: /*
1509: * kernfs_unlink_sibling() succeeds once per node. Use it
1510: * to decide who's responsible for cleanups.
1511: */
1512: if (!parent || kernfs_unlink_sibling(pos)) {
1513: struct kernfs_iattrs *ps_iattr =
1514: parent ? parent->iattr : NULL;
1515:
1516: /* update timestamps on the parent */
1517: down_write(&kernfs_root(kn)->kernfs_iattr_rwsem);
1518:
1519: if (ps_iattr) {
1520: ktime_get_real_ts64(&ps_iattr->ia_ctime);
1521: ps_iattr->ia_mtime = ps_iattr->ia_ctime;
1522: }
1523:
1524: up_write(&kernfs_root(kn)->kernfs_iattr_rwsem);
1525: kernfs_put(pos);
1526: }
1527:
1528: kernfs_put(pos);
1529: } while (pos != kn);
1530: }
SourceFile:fs/kernfs/dir.c]
|
| 74/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 56447
output: 36
thoughts: 26
Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, aimed at an expert audience and in the first person:
**Draining Kernel Filesystems: A Deep Dive**
Right, so `kernfs_drain`... that's the one that's supposed to handle the waiting, isn't it? Specifically, it's designed to ensure that all active references to the kernel filesystem are cleaned up before something else happens. I need to be absolutely sure about that.
Let me just quickly verify my understanding. It's crucial, obviously. If it *doesn't* wait properly, that's a recipe for serious corruption and data loss. I'll need to go back and double check `kernfs_drain` to be completely confident.
|
| 75/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/dir.c Name:kernfs_drain]
Results: map[SourceCode: 481: /**
482: * kernfs_drain - drain kernfs_node
483: * @kn: kernfs_node to drain
484: *
485: * Drain existing usages and nuke all existing mmaps of @kn. Multiple
486: * removers may invoke this function concurrently on @kn and all will
487: * return after draining is complete.
488: */
489: static void kernfs_drain(struct kernfs_node *kn)
490: __releases(&kernfs_root(kn)->kernfs_rwsem)
491: __acquires(&kernfs_root(kn)->kernfs_rwsem)
492: {
493: struct kernfs_root *root = kernfs_root(kn);
494:
495: lockdep_assert_held_write(&root->kernfs_rwsem);
496: WARN_ON_ONCE(kernfs_active(kn));
497:
498: /*
499: * Skip draining if already fully drained. This avoids draining and its
500: * lockdep annotations for nodes which have never been activated
501: * allowing embedding kernfs_remove() in create error paths without
502: * worrying about draining.
503: */
504: if (atomic_read(&kn->active) == KN_DEACTIVATED_BIAS &&
505: !kernfs_should_drain_open_files(kn))
506: return;
507:
508: up_write(&root->kernfs_rwsem);
509:
510: if (kernfs_lockdep(kn)) {
511: rwsem_acquire(&kn->dep_map, 0, 0, _RET_IP_);
512: if (atomic_read(&kn->active) != KN_DEACTIVATED_BIAS)
513: lock_contended(&kn->dep_map, _RET_IP_);
514: }
515:
516: wait_event(root->deactivate_waitq,
517: atomic_read(&kn->active) == KN_DEACTIVATED_BIAS);
518:
519: if (kernfs_lockdep(kn)) {
520: lock_acquired(&kn->dep_map, _RET_IP_);
521: rwsem_release(&kn->dep_map, _RET_IP_);
522: }
523:
524: if (kernfs_should_drain_open_files(kn))
525: kernfs_drain_open_files(kn);
526:
527: down_write(&root->kernfs_rwsem);
528: }
SourceFile:fs/kernfs/dir.c]
|
| 76/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 57471
output: 38
thoughts: 222
Thoughts: **The Kernfs Drain Conundrum**
Okay, here's the issue that's just hit me. I'm looking at `kernfs_drain`, and I see it's calling `wait_event` on `root->deactivate_waitq`, specifically waiting for `atomic_read(&kn->active)` to equal `KN_DEACTIVATED_BIAS`. That's the key. Now, `kernfs_get_active` *increments* `kn->active`. So, if a user is currently inside a `read` or `write` operation on a sysfs file, `kernfs_drain` is going to *block*. And, of course, `kernfs_drain` is called during `device_unregister`!
So, the potential problem is clear: if a user can block *inside* a `read` or `write` operation on a sysfs file, they could effectively block `device_unregister`! That's... not good.
The next question is, can they block? The answer is unfortunately yes, if the sysfs `show` or `store` method does any blocking, or, potentially worse, if the user leverages userfaultfd on the buffer passed to read or write.
Okay, let's step back and look at where this is happening. `sysfs_kf_read` calls the `show` method. That's the critical path. Time to dive into `sysfs_kf_read` to see what's going on.
|
| 77/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/sysfs/file.c Name:sysfs_kf_read]
Results: map[SourceCode: 109: /* kernfs read callback for regular sysfs files with pre-alloc */
110: static ssize_t sysfs_kf_read(struct kernfs_open_file *of, char *buf,
111: size_t count, loff_t pos)
112: {
113: const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
114: struct kobject *kobj = sysfs_file_kobj(of->kn);
115: ssize_t len;
116:
117: /*
118: * If buf != of->prealloc_buf, we don't know how
119: * large it is, so cannot safely pass it to ->show
120: */
121: if (WARN_ON_ONCE(buf != of->prealloc_buf))
122: return 0;
123: len = ops->show(kobj, of->kn->priv, buf);
124: if (len < 0)
125: return len;
126: if (pos) {
127: if (len <= pos)
128: return 0;
129: len -= pos;
130: memmove(buf, buf + pos, len);
131: }
132: return min_t(ssize_t, count, len);
133: }
SourceFile:fs/sysfs/file.c]
|
| 78/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 58484
output: 41
thoughts: 99
Thoughts: **Analyzing the Data Flow of a Kernel File Read**
Okay, so I'm tracing through this `sysfs_kf_read` function. It looks like it calls `ops->show(kobj, of->kn->priv, buf)`. The key thing here is that the buffer `buf` it's passing, `of->prealloc_buf`, is a *kernel* buffer. This throws a major wrench in the potential for using userfaultfd to handle any blocking operations within `ops->show`. Since the data is already in kernel space, there's no way to trigger a user-space fault before the read. The transfer to user space must be happening *after* this call.
Alright, I need to pinpoint where the user-space copy actually occurs. That implies it's somewhere else in the read process, namely in `kernfs_fop_read_iter`. Let me go check that out.
|
| 79/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_fop_read_iter]
Results: map[SourceCode: 276: static ssize_t kernfs_fop_read_iter(struct kiocb *iocb, struct iov_iter *iter)
277: {
278: if (kernfs_of(iocb->ki_filp)->kn->flags & KERNFS_HAS_SEQ_SHOW)
279: return seq_read_iter(iocb, iter);
280: return kernfs_file_read_iter(iocb, iter);
281: }
SourceFile:fs/kernfs/file.c]
|
| 80/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 58981
output: 40
thoughts: 14
Thoughts: Okay, let's dive into this. Here's my thought process on what we're looking at, summarized in the first person, assuming I'm already pretty familiar with file system internals:
**Diving into `kernfs_file_read_iter`**
Alright, so the task at hand is `kernfs_file_read_iter`. Let's get right to it. I immediately think: "This is a key function in the `kernfs` framework, and likely handles the core read operation from a file within a `kernfs` filesystem." I need to understand exactly what this is doing at a fundamental level. I'm imagining how data is flowing, which structures are involved, and where the potential bottlenecks might be. I'm wondering about the interaction with the underlying storage and the different levels of abstraction that are implemented within `kernfs`. My goal is to grasp how it manages iterating through the data, handles buffers, and interacts with the VFS layer, if applicable. I also need to be on the lookout for potential optimizations and thread-safety considerations. I will now analyze the code to determine the intricacies of this procedure.
|
| 81/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_file_read_iter]
Results: map[SourceCode: 215: /*
216: * As reading a bin file can have side-effects, the exact offset and bytes
217: * specified in read(2) call should be passed to the read callback making
218: * it difficult to use seq_file. Implement simplistic custom buffering for
219: * bin files.
220: */
221: static ssize_t kernfs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
222: {
223: struct kernfs_open_file *of = kernfs_of(iocb->ki_filp);
224: ssize_t len = min_t(size_t, iov_iter_count(iter), PAGE_SIZE);
225: const struct kernfs_ops *ops;
226: char *buf;
227:
228: buf = of->prealloc_buf;
229: if (buf)
230: mutex_lock(&of->prealloc_mutex);
231: else
232: buf = kmalloc(len, GFP_KERNEL);
233: if (!buf)
234: return -ENOMEM;
235:
236: /*
237: * @of->mutex nests outside active ref and is used both to ensure that
238: * the ops aren't called concurrently for the same open file.
239: */
240: mutex_lock(&of->mutex);
241: if (!kernfs_get_active(of->kn)) {
242: len = -ENODEV;
243: mutex_unlock(&of->mutex);
244: goto out_free;
245: }
246:
247: of->event = atomic_read(&of_on(of)->event);
248:
249: ops = kernfs_ops(of->kn);
250: if (ops->read)
251: len = ops->read(of, buf, len, iocb->ki_pos);
252: else
253: len = -EINVAL;
254:
255: kernfs_put_active(of->kn);
256: mutex_unlock(&of->mutex);
257:
258: if (len < 0)
259: goto out_free;
260:
261: if (copy_to_iter(buf, len, iter) != len) {
262: len = -EFAULT;
263: goto out_free;
264: }
265:
266: iocb->ki_pos += len;
267:
268: out_free:
269: if (buf == of->prealloc_buf)
270: mutex_unlock(&of->prealloc_mutex);
271: else
272: kfree(buf);
273: return len;
274: }
SourceFile:fs/kernfs/file.c]
|
| 82/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 60160
output: 38
thoughts: 66
Thoughts: Okay, let me think this through...
**Analyzing the Reference Count in the Context of `userfaultfd` and Iterators**
Right, so I've been digging into this, and I've just had a significant realization. The call to `kernfs_put_active(of->kn)` *precedes* the crucial `copy_to_iter(buf, len, iter)` function. This means that a `userfaultfd` situation arising within `copy_to_iter` will *not* be blocked by the active reference. The kernel will essentially release this reference prematurely. That's a critical point for understanding potential race conditions and memory access issues with these iterators. This could be a significant design flaw...or perhaps an optimization...depending on context.
Now, my mind shifts to `seq_read_iter`. Does `seq_read_iter` have the same characteristic? I wonder if its implementation follows a similar sequence of operations, or if it handles reference counts more conservatively, perhaps holding the reference for the entire copy operation. I need to investigate that code path.
|
| 83/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_seq_start]
Results: map[SourceCode: 145: static void *kernfs_seq_start(struct seq_file *sf, loff_t *ppos)
146: {
147: struct kernfs_open_file *of = sf->private;
148: const struct kernfs_ops *ops;
149:
150: /*
151: * @of->mutex nests outside active ref and is primarily to ensure that
152: * the ops aren't called concurrently for the same open file.
153: */
154: mutex_lock(&of->mutex);
155: if (!kernfs_get_active(of->kn))
156: return ERR_PTR(-ENODEV);
157:
158: ops = kernfs_ops(of->kn);
159: if (ops->seq_start) {
160: void *next = ops->seq_start(sf, ppos);
161: /* see the comment above kernfs_seq_stop_active() */
162: if (next == ERR_PTR(-ENODEV))
163: kernfs_seq_stop_active(sf, next);
164: return next;
165: }
166: return single_start(sf, ppos);
167: }
SourceFile:fs/kernfs/file.c]
|
| 84/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 60884
output: 38
thoughts: 9
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Kernel Filesystem Sequence Iteration Halting**
Alright, I'm thinking about `kernfs_seq_stop`. It's crucial for properly managing the lifecycle of sequence operations within the kernel filesystem framework. Basically, it's the point where we need to gracefully shut down the iteration, handling resource cleanup and ensuring everything is in a consistent state. We have to consider the scenarios where the iteration might be incomplete, perhaps due to an error during processing or the user requesting an early stop. The details surrounding what specifically needs to be cleaned up are dependent on the iterator's data and state.
We have to handle potential issues. I'm wondering about the implications of interrupts. We may need to ensure atomicity in order to avoid corruption, so what kind of locks may be held at this point? Does `kernfs_seq_stop` need to release any locks, decrement reference counts, or free memory associated with the sequence data? The specific actions are dictated by the structure and management of the underlying sequence context. We need to avoid memory leaks. The structure of the iterator, or the structure that is being iterated, determine what needs to be freed, unmapped, released or unlocked.
I need to quickly recall the typical usage patterns of this function. I'm going through the different sequence iterator types in my head to determine common tasks. One concern is always ensuring that we don't hold any locks or resources that might block other operations, especially in interrupt contexts. I need to make sure we're considering all possible scenarios: the iteration might be stopped due to an error, a user request, or the iterator naturally reaching the end of its data. We need to make sure we're handling all of those cases gracefully, and returning the proper value.
|
| 85/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_seq_stop]
Results: map[SourceCode: 190: static void kernfs_seq_stop(struct seq_file *sf, void *v)
191: {
192: struct kernfs_open_file *of = sf->private;
193:
194: if (v != ERR_PTR(-ENODEV))
195: kernfs_seq_stop_active(sf, v);
196: mutex_unlock(&of->mutex);
197: }
SourceFile:fs/kernfs/file.c]
|
| 86/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 61475
output: 40
thoughts: 11
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Kernel Filesystem Sequence Iteration Halting**
Alright, I'm thinking about `kernfs_seq_stop_active`. It's pretty straightforward, but crucial. We're in the middle of a sequence iteration within the kernel filesystem, and this function is the safety net, the mechanism to gracefully halt the process. My mental model here involves several key aspects.
First, I need to understand *why* we're stopping. Is it an error condition? Did the iteration reach its natural end? Or, more subtly, did some external event interrupt the process, and we need to release our hold on resources and potentially inform higher layers? I need to quickly assess this context to proceed.
Second, the function's name implies we're dealing with an "active" sequence. Therefore I must consider what resources are currently *active*, or in use, by this sequence. Memory allocations, locks, file descriptors - all these need to be carefully cleaned up or released to prevent leaks or race conditions.
I'm mentally stepping through a check list:
* **Is this a clean exit?** or a mid-iteration error? This determines if we need to release resources only, or attempt to log and manage the problem.
* **Locks:** Identify any locks held by the sequence. These might be spinlocks, mutexes, read-write locks, etc. Proper unlocking is paramount to avoid deadlocks.
* **Memory Management:** Deallocate any dynamically allocated memory specifically used in this sequence (e.g. allocated iterators, buffers, etc). We'll want to ensure this doesn't lead to issues further up the stack.
* **File Descriptors:** Are we holding any? Proper closing is an obvious requirement in general, but I'm thinking specifically about any opened as part of this sequence, that now need to be released.
Essentially, I'm meticulously unwinding the state established during the sequence, ensuring that any resources used are freed up and that the system is left in a consistent state. It's a fundamental part of robust filesystem operations, preventing resource exhaustion and ensuring the integrity of the kernel. This is a crucial area for any in-depth kernel debugging.
|
| 87/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/kernfs/file.c Name:kernfs_seq_stop_active]
Results: map[SourceCode: 113: /*
114: * As kernfs_seq_stop() is also called after kernfs_seq_start() or
115: * kernfs_seq_next() failure, it needs to distinguish whether it's stopping
116: * a seq_file iteration which is fully initialized with an active reference
117: * or an aborted kernfs_seq_start() due to get_active failure. The
118: * position pointer is the only context for each seq_file iteration and
119: * thus the stop condition should be encoded in it. As the return value is
120: * directly visible to userland, ERR_PTR(-ENODEV) is the only acceptable
121: * choice to indicate get_active failure.
122: *
123: * Unfortunately, this is complicated due to the optional custom seq_file
124: * operations which may return ERR_PTR(-ENODEV) too. kernfs_seq_stop()
125: * can't distinguish whether ERR_PTR(-ENODEV) is from get_active failure or
126: * custom seq_file operations and thus can't decide whether put_active
127: * should be performed or not only on ERR_PTR(-ENODEV).
128: *
129: * This is worked around by factoring out the custom seq_stop() and
130: * put_active part into kernfs_seq_stop_active(), skipping it from
131: * kernfs_seq_stop() if ERR_PTR(-ENODEV) while invoking it directly after
132: * custom seq_file operations fail with ERR_PTR(-ENODEV) - this ensures
133: * that kernfs_seq_stop_active() is skipped only after get_active failure.
134: */
135: static void kernfs_seq_stop_active(struct seq_file *sf, void *v)
136: {
137: struct kernfs_open_file *of = sf->private;
138: const struct kernfs_ops *ops = kernfs_ops(of->kn);
139:
140: if (ops->seq_stop)
141: ops->seq_stop(sf, v);
142: kernfs_put_active(of->kn);
143: }
SourceFile:fs/kernfs/file.c]
|
| 88/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 62665
output: 36
thoughts: 87
Thoughts: Here's my thought process, as it stands:
**Examining the Sequencing of Read Operations**
Okay, so I'm tracing through this read process and things are starting to click. I'm seeing that `seq_read_iter` is the critical function here. It's calling *either* `seq_read` or `seq_read_iter` itself, which, in turn, ultimately calls the method pointers of `m->op` - specifically `start`, `show`, and `stop`. That makes perfect sense for setting up, displaying content, and cleaning up the sequence.
But the real kicker here... I just realized `copy_to_user` is *happening inside* `seq_read_iter`! That's the part that's actually putting the data into userspace. This implies that the sequence-specific operations must be generating the data in kernel space, and `seq_read_iter` is then responsible for the transfer. It's time to confirm my assumption by checking the source code of `seq_read_iter` in `fs/seq_file.c`. I need to solidify this mental model and ensure my understanding of the data transfer is correct.
|
| 89/2 |
2026/05/30 16:45 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/seq_file.c Name:seq_read_iter]
Results: map[SourceCode: 168: /*
169: * Ready-made ->f_op->read_iter()
170: */
171: ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
172: {
173: struct seq_file *m = iocb->ki_filp->private_data;
174: size_t copied = 0;
175: size_t n;
176: void *p;
177: int err = 0;
178:
179: if (!iov_iter_count(iter))
180: return 0;
181:
182: mutex_lock(&m->lock);
183:
184: /*
185: * if request is to read from zero offset, reset iterator to first
186: * record as it might have been already advanced by previous requests
187: */
188: if (iocb->ki_pos == 0) {
189: m->index = 0;
190: m->count = 0;
191: }
192:
193: /* Don't assume ki_pos is where we left it */
194: if (unlikely(iocb->ki_pos != m->read_pos)) {
195: while ((err = traverse(m, iocb->ki_pos)) == -EAGAIN)
196: ;
197: if (err) {
198: /* With prejudice... */
199: m->read_pos = 0;
200: m->index = 0;
201: m->count = 0;
202: goto Done;
203: } else {
204: m->read_pos = iocb->ki_pos;
205: }
206: }
207:
208: /* grab buffer if we didn't have one */
209: if (!m->buf) {
210: m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
211: if (!m->buf)
212: goto Enomem;
213: }
214: // something left in the buffer - copy it out first
215: if (m->count) {
216: n = copy_to_iter(m->buf + m->from, m->count, iter);
217: m->count -= n;
218: m->from += n;
219: copied += n;
220: if (m->count) // hadn't managed to copy everything
221: goto Done;
222: }
223: // get a non-empty record in the buffer
224: m->from = 0;
225: p = m->op->start(m, &m->index);
226: while (1) {
227: err = PTR_ERR(p);
228: if (!p || IS_ERR(p)) // EOF or an error
229: break;
230: err = m->op->show(m, p);
231: if (err < 0) // hard error
232: break;
233: if (unlikely(err)) // ->show() says "skip it"
234: m->count = 0;
235: if (unlikely(!m->count)) { // empty record
236: p = m->op->next(m, p, &m->index);
237: continue;
238: }
239: if (!seq_has_overflowed(m)) // got it
240: goto Fill;
241: // need a bigger buffer
242: m->op->stop(m, p);
243: kvfree(m->buf);
244: m->count = 0;
245: m->buf = seq_buf_alloc(m->size <<= 1);
246: if (!m->buf)
247: goto Enomem;
248: p = m->op->start(m, &m->index);
249: }
250: // EOF or an error
251: m->op->stop(m, p);
252: m->count = 0;
253: goto Done;
254: Fill:
255: // one non-empty record is in the buffer; if they want more,
256: // try to fit more in, but in any case we need to advance
257: // the iterator once for every record shown.
258: while (1) {
259: size_t offs = m->count;
260: loff_t pos = m->index;
261:
262: p = m->op->next(m, p, &m->index);
263: if (pos == m->index) {
264: pr_info_ratelimited("buggy .next function %ps did not update position index\n",
265: m->op->next);
266: m->index++;
267: }
268: if (!p || IS_ERR(p)) // no next record for us
269: break;
270: if (m->count >= iov_iter_count(iter))
271: break;
272: err = m->op->show(m, p);
273: if (err > 0) { // ->show() says "skip it"
274: m->count = offs;
275: } else if (err || seq_has_overflowed(m)) {
276: m->count = offs;
277: break;
278: }
279: }
280: m->op->stop(m, p);
281: n = copy_to_iter(m->buf, m->count, iter);
282: copied += n;
283: m->count -= n;
284: m->from = n;
285: Done:
286: if (unlikely(!copied)) {
287: copied = m->count ? -EFAULT : err;
288: } else {
289: iocb->ki_pos += copied;
290: m->read_pos += copied;
291: }
292: mutex_unlock(&m->lock);
293: return copied;
294: Enomem:
295: err = -ENOMEM;
296: goto Done;
297: }
SourceFile:fs/seq_file.c]
|
| 90/2 |
2026/05/30 16:45 |
llm |
expert |
0m
Model:
Tokens:
input: 64977
output: 34
thoughts: 546
Thoughts: **Analyzing Device Unregistration Vulnerabilities**
Okay, let's break this down. My initial thought was that a user might be able to block `device_unregister` on a sysfs file by using userfaultfd within a `read` or `write` operation. But hold on, the `seq_read_iter` sequence of calls throws a wrench into that idea. It seems `m->op->stop` which calls `kernfs_seq_stop` and then `kernfs_put_active` (dropping the active reference) happens *before* `copy_to_iter`. This immediately eliminates the userfaultfd blocking approach for `kernfs_drain`. So, that doesn't work.
This implies the only method to block `device_unregister` is to maintain a reference to the `struct device` directly. From a user-space perspective, the only obvious way to get a reference is by opening a device node that holds a reference to the target device. In the specific case of an `i2c_adapter`, `/dev/i2c-#` is a plausible candidate. However, this is restricted to root or the `i2c` group. So, that's not viable for an unprivileged user.
I'm now thinking, what about other device nodes? For instance, does opening a V4L2 device node indirectly hold a reference to the `i2c_adapter`? I don't think so. It holds a reference to the V4L2 device, which in turn holds a reference to the USB device. Although `i2c_del_adapter` is eventually called for the i2c adapter.
Wait, if `i2c_del_adapter` is indeed called, it should wait for the i2c adapter device to be released. And the i2c adapter is a child of the USB device. So the question becomes, if a user holds a V4L2 device node, does that block the unregistration of the i2c adapter. My current thinking is that no, because the V4L2 node holds a reference to the V4L2 device, not the i2c adapter.
But wait, what if the user opens a device node *of* an i2c client? For example, an i2c client device is registered on the i2c adapter. If the user opens the device node of that i2c client, like `/dev/rtc0`, does it maintain a reference to the i2c adapter device? Yes, that's it! An i2c client device is a child of the i2c adapter device. When a child device is registered, it necessarily takes a reference to its parent. I need to confirm this with `device_add`, to be completely certain.
|
| 91/2 |
2026/05/30 16:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/base/core.c Name:device_add]
Results: map[SourceCode:3547: /**
3548: * device_add - add device to device hierarchy.
3549: * @dev: device.
3550: *
3551: * This is part 2 of device_register(), though may be called
3552: * separately _iff_ device_initialize() has been called separately.
3553: *
3554: * This adds @dev to the kobject hierarchy via kobject_add(), adds it
3555: * to the global and sibling lists for the device, then
3556: * adds it to the other relevant subsystems of the driver model.
3557: *
3558: * Do not call this routine or device_register() more than once for
3559: * any device structure. The driver model core is not designed to work
3560: * with devices that get unregistered and then spring back to life.
3561: * (Among other things, it's very hard to guarantee that all references
3562: * to the previous incarnation of @dev have been dropped.) Allocate
3563: * and register a fresh new struct device instead.
3564: *
3565: * NOTE: _Never_ directly free @dev after calling this function, even
3566: * if it returned an error! Always use put_device() to give up your
3567: * reference instead.
3568: *
3569: * Rule of thumb is: if device_add() succeeds, you should call
3570: * device_del() when you want to get rid of it. If device_add() has
3571: * *not* succeeded, use *only* put_device() to drop the reference
3572: * count.
3573: */
3574: int device_add(struct device *dev)
3575: {
3576: struct subsys_private *sp;
3577: struct device *parent;
3578: struct kobject *kobj;
3579: struct class_interface *class_intf;
3580: int error = -EINVAL;
3581: struct kobject *glue_dir = NULL;
3582:
3583: dev = get_device(dev);
3584: if (!dev)
3585: goto done;
3586:
3587: if (!dev->p) {
3588: error = device_private_init(dev);
3589: if (error)
3590: goto done;
3591: }
3592:
3593: /*
3594: * for statically allocated devices, which should all be converted
3595: * some day, we need to initialize the name. We prevent reading back
3596: * the name, and force the use of dev_name()
3597: */
3598: if (dev->init_name) {
3599: error = dev_set_name(dev, "%s", dev->init_name);
3600: dev->init_name = NULL;
3601: }
3602:
3603: if (dev_name(dev))
3604: error = 0;
3605: /* subsystems can specify simple device enumeration */
3606: else if (dev->bus && dev->bus->dev_name)
3607: error = dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
3608: else
3609: error = -EINVAL;
3610: if (error)
3611: goto name_error;
3612:
3613: pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
3614:
3615: parent = get_device(dev->parent);
3616: kobj = get_device_parent(dev, parent);
3617: if (IS_ERR(kobj)) {
3618: error = PTR_ERR(kobj);
3619: goto parent_error;
3620: }
3621: if (kobj)
3622: dev->kobj.parent = kobj;
3623:
3624: /* use parent numa_node */
3625: if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
3626: set_dev_node(dev, dev_to_node(parent));
3627:
3628: /* first, register with generic layer. */
3629: /* we require the name to be set before, and pass NULL */
3630: error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
3631: if (error) {
3632: glue_dir = kobj;
3633: goto Error;
3634: }
3635:
3636: /* notify platform of device entry */
3637: device_platform_notify(dev);
3638:
3639: error = device_create_file(dev, &dev_attr_uevent);
3640: if (error)
3641: goto attrError;
3642:
3643: error = device_add_class_symlinks(dev);
3644: if (error)
3645: goto SymlinkError;
3646: error = device_add_attrs(dev);
3647: if (error)
3648: goto AttrsError;
3649: error = bus_add_device(dev);
3650: if (error)
3651: goto BusError;
3652: error = dpm_sysfs_add(dev);
3653: if (error)
3654: goto DPMError;
3655: device_pm_add(dev);
3656:
3657: if (MAJOR(dev->devt)) {
3658: error = device_create_file(dev, &dev_attr_dev);
3659: if (error)
3660: goto DevAttrError;
3661:
3662: error = device_create_sys_dev_entry(dev);
3663: if (error)
3664: goto SysEntryError;
3665:
3666: devtmpfs_create_node(dev);
3667: }
3668:
3669: /* Notify clients of device addition. This call must come
3670: * after dpm_sysfs_add() and before kobject_uevent().
3671: */
3672: bus_notify(dev, BUS_NOTIFY_ADD_DEVICE);
3673: kobject_uevent(&dev->kobj, KOBJ_ADD);
3674:
3675: /*
3676: * Check if any of the other devices (consumers) have been waiting for
3677: * this device (supplier) to be added so that they can create a device
3678: * link to it.
3679: *
3680: * This needs to happen after device_pm_add() because device_link_add()
3681: * requires the supplier be registered before it's called.
3682: *
3683: * But this also needs to happen before bus_probe_device() to make sure
3684: * waiting consumers can link to it before the driver is bound to the
3685: * device and the driver sync_state callback is called for this device.
3686: */
3687: if (dev->fwnode && !dev->fwnode->dev) {
3688: dev->fwnode->dev = dev;
3689: fw_devlink_link_device(dev);
3690: }
3691:
3692: bus_probe_device(dev);
3693:
3694: /*
3695: * If all driver registration is done and a newly added device doesn't
3696: * match with any driver, don't block its consumers from probing in
3697: * case the consumer device is able to operate without this supplier.
3698: */
3699: if (dev->fwnode && fw_devlink_drv_reg_done && !dev->can_match)
3700: fw_devlink_unblock_consumers(dev);
3701:
3702: if (parent)
3703: klist_add_tail(&dev->p->knode_parent,
3704: &parent->p->klist_children);
3705:
3706: sp = class_to_subsys(dev->class);
3707: if (sp) {
3708: mutex_lock(&sp->mutex);
3709: /* tie the class to the device */
3710: klist_add_tail(&dev->p->knode_class, &sp->klist_devices);
3711:
3712: /* notify any interfaces that the device is here */
3713: list_for_each_entry(class_intf, &sp->interfaces, node)
3714: if (class_intf->add_dev)
3715: class_intf->add_dev(dev);
3716: mutex_unlock(&sp->mutex);
3717: subsys_put(sp);
3718: }
3719: done:
3720: put_device(dev);
3721: return error;
3722: SysEntryError:
3723: if (MAJOR(dev->devt))
3724: device_remove_file(dev, &dev_attr_dev);
3725: DevAttrError:
3726: device_pm_remove(dev);
3727: dpm_sysfs_remove(dev);
3728: DPMError:
3729: device_set_driver(dev, NULL);
3730: bus_remove_device(dev);
3731: BusError:
3732: device_remove_attrs(dev);
3733: AttrsError:
3734: device_remove_class_symlinks(dev);
3735: SymlinkError:
3736: device_remove_file(dev, &dev_attr_uevent);
3737: attrError:
3738: device_platform_notify_remove(dev);
3739: kobject_uevent(&dev->kobj, KOBJ_REMOVE);
3740: glue_dir = get_glue_dir(dev);
3741: kobject_del(&dev->kobj);
3742: Error:
3743: cleanup_glue_dir(dev, glue_dir);
3744: parent_error:
3745: put_device(parent);
3746: name_error:
3747: kfree(dev->p);
3748: dev->p = NULL;
3749: goto done;
3750: }
SourceFile:drivers/base/core.c]
|
| 92/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 69209
output: 34
thoughts: 395
Thoughts: **Analyzing Device Registration and Unregistration in the I2C Subsystem**
Okay, so let me break this down. It seems pretty straightforward on the surface, but there are some nuances that need attention. The `device_add` function calls `get_device` to fetch the parent device, then `kobj = get_device_parent`. This establishes a parent-child relationship, right? And `kobject_add` takes a reference to the parent's `kobject`. Therefore, the child device *does* indeed hold a reference to its parent. Specifically, with an i2c client device, that means it holds a reference to the i2c adapter device.
Now, here's where it gets interesting. `i2c_del_adapter` completely unregisters *all* i2c client devices. This is done with the `device_for_each_child` function, calling `__unregister_client`. Which, in turn, calls `i2c_unregister_device` and finally `device_unregister`. This calls `device_del` and ultimately `put_device`. So, the child is unregistered, and its reference to the parent is dropped. Makes sense.
But what if there's a user space process in the mix? Like, what if a user is actively using the `/dev/rtc0` device? If they have that device open, they hold a reference to the RTC device (specifically `rtc->dev`). This RTC device holds a reference to *its* parent, which is the i2c client device, which in turn holds a reference to the i2c adapter.
The problem lies with how the i2c adapter is unregistered. When `i2c_del_adapter` is called, it unregisters the i2c client devices. The i2c client unregisters the RTC device. The main question here is: does `rtc_device_unregister` block or not? No. So it seems that there is an issue with the underlying unregistering mechanism.
But, hang on... `device_unregister` only *drops* the reference to the parent *when the device is released*! So `device_del` does not automatically drop the reference, even though it appears to in this context. I think I need to look closer at what `device_del` actually does. I need to refresh myself about that. Let's check the code for `device_del`.
|
| 93/2 |
2026/05/30 16:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/base/core.c Name:device_del]
Results: map[SourceCode:3822: /**
3823: * device_del - delete device from system.
3824: * @dev: device.
3825: *
3826: * This is the first part of the device unregistration
3827: * sequence. This removes the device from the lists we control
3828: * from here, has it removed from the other driver model
3829: * subsystems it was added to in device_add(), and removes it
3830: * from the kobject hierarchy.
3831: *
3832: * NOTE: this should be called manually _iff_ device_add() was
3833: * also called manually.
3834: */
3835: void device_del(struct device *dev)
3836: {
3837: struct subsys_private *sp;
3838: struct device *parent = dev->parent;
3839: struct kobject *glue_dir = NULL;
3840: struct class_interface *class_intf;
3841: unsigned int noio_flag;
3842:
3843: device_lock(dev);
3844: kill_device(dev);
3845: device_unlock(dev);
3846:
3847: if (dev->fwnode && dev->fwnode->dev == dev)
3848: dev->fwnode->dev = NULL;
3849:
3850: /* Notify clients of device removal. This call must come
3851: * before dpm_sysfs_remove().
3852: */
3853: noio_flag = memalloc_noio_save();
3854: bus_notify(dev, BUS_NOTIFY_DEL_DEVICE);
3855:
3856: dpm_sysfs_remove(dev);
3857: if (parent)
3858: klist_del(&dev->p->knode_parent);
3859: if (MAJOR(dev->devt)) {
3860: devtmpfs_delete_node(dev);
3861: device_remove_sys_dev_entry(dev);
3862: device_remove_file(dev, &dev_attr_dev);
3863: }
3864:
3865: sp = class_to_subsys(dev->class);
3866: if (sp) {
3867: device_remove_class_symlinks(dev);
3868:
3869: mutex_lock(&sp->mutex);
3870: /* notify any interfaces that the device is now gone */
3871: list_for_each_entry(class_intf, &sp->interfaces, node)
3872: if (class_intf->remove_dev)
3873: class_intf->remove_dev(dev);
3874: /* remove the device from the class list */
3875: klist_del(&dev->p->knode_class);
3876: mutex_unlock(&sp->mutex);
3877: subsys_put(sp);
3878: }
3879: device_remove_file(dev, &dev_attr_uevent);
3880: device_remove_attrs(dev);
3881: bus_remove_device(dev);
3882: device_pm_remove(dev);
3883: driver_deferred_probe_del(dev);
3884: device_platform_notify_remove(dev);
3885: device_links_purge(dev);
3886:
3887: /*
3888: * If a device does not have a driver attached, we need to clean
3889: * up any managed resources. We do this in device_release(), but
3890: * it's never called (and we leak the device) if a managed
3891: * resource holds a reference to the device. So release all
3892: * managed resources here, like we do in driver_detach(). We
3893: * still need to do so again in device_release() in case someone
3894: * adds a new resource after this point, though.
3895: */
3896: devres_release_all(dev);
3897:
3898: bus_notify(dev, BUS_NOTIFY_REMOVED_DEVICE);
3899: kobject_uevent(&dev->kobj, KOBJ_REMOVE);
3900: glue_dir = get_glue_dir(dev);
3901: kobject_del(&dev->kobj);
3902: cleanup_glue_dir(dev, glue_dir);
3903: memalloc_noio_restore(noio_flag);
3904: put_device(parent);
3905: }
SourceFile:drivers/base/core.c]
|
| 94/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 71486
output: 34
thoughts: 71
Thoughts: **Analyzing Device Driver Reference Counting**
Okay, hold on a second. I'm following the flow here and something just clicked. I see that the `device_del` function, when it's called to remove a device, is ultimately calling `put_device(parent)` at the very end. That's the expected cleanup. However, I've got to ensure I understand how this relates to the creation and lifecycle.
Now, let's cast our mind back. In the `device_add` function, we called `parent = get_device(dev->parent);` to get the parent device, and then called `kobject_add`, but `kobject_add` itself also takes a *reference* to the parent kobject. My spidey-sense is tingling. That looks like a potential double-reference issue if we're not careful. I need to be absolutely sure that the reference counting is correct here. We need to prevent a double free!
Let me make sure. I'm going to jump into a quick code dive and double-check exactly how `kobject_add` is handling that parent kobject reference. It's the key to the whole dance, and I want to be *certain* that we're releasing the reference appropriately in all cases to avoid memory corruption.
|
| 95/2 |
2026/05/30 16:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:lib/kobject.c Name:kobject_add]
Results: map[SourceCode: 377: /**
378: * kobject_add() - The main kobject add function.
379: * @kobj: the kobject to add
380: * @parent: pointer to the parent of the kobject.
381: * @fmt: format to name the kobject with.
382: *
383: * The kobject name is set and added to the kobject hierarchy in this
384: * function.
385: *
386: * If @parent is set, then the parent of the @kobj will be set to it.
387: * If @parent is NULL, then the parent of the @kobj will be set to the
388: * kobject associated with the kset assigned to this kobject. If no kset
389: * is assigned to the kobject, then the kobject will be located in the
390: * root of the sysfs tree.
391: *
392: * Note, no "add" uevent will be created with this call, the caller should set
393: * up all of the necessary sysfs files for the object and then call
394: * kobject_uevent() with the UEVENT_ADD parameter to ensure that
395: * userspace is properly notified of this kobject's creation.
396: *
397: * Return: If this function returns an error, kobject_put() must be
398: * called to properly clean up the memory associated with the
399: * object. Under no instance should the kobject that is passed
400: * to this function be directly freed with a call to kfree(),
401: * that can leak memory.
402: *
403: * If this function returns success, kobject_put() must also be called
404: * in order to properly clean up the memory associated with the object.
405: *
406: * In short, once this function is called, kobject_put() MUST be called
407: * when the use of the object is finished in order to properly free
408: * everything.
409: */
410: int kobject_add(struct kobject *kobj, struct kobject *parent,
411: const char *fmt, ...)
412: {
413: va_list args;
414: int retval;
415:
416: if (!kobj)
417: return -EINVAL;
418:
419: if (!kobj->state_initialized) {
420: pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",
421: kobject_name(kobj), kobj);
422: dump_stack_lvl(KERN_ERR);
423: return -EINVAL;
424: }
425: va_start(args, fmt);
426: retval = kobject_add_varg(kobj, parent, fmt, args);
427: va_end(args);
428:
429: return retval;
430: }
SourceFile:lib/kobject.c]
|
| 96/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 72783
output: 34
thoughts: 168
Thoughts: **The I2C Adapter and Client Device Conundrum**
Okay, hold on a second. I'm following the code flow here, and something feels off. I'm looking at how `kobject_add_varg` works; it grabs a reference to the parent `kobj` with `kobject_get(kobj->parent)`. And then, `kobject_del` releases that reference with `kobject_put(kobj->parent)`. So, that means when `device_del` gets called... and it does get called from `i2c_del_adapter` when it calls `device_unregister(&client->dev)`… it *immediately* drops the reference to the parent I2C adapter!
This has some significant implications. Essentially, the act of deleting the client device – even if a user space application is still holding a reference to it (say, like having `/dev/rtc0` open) – causes the *client device's* internal reference to the I2C adapter to be released right then and there. That's unexpected behavior, and potentially a major source of race conditions and dangling pointers. We should be sure that is really what is happening.
Let me just quickly verify this by double checking the implementation of `kobject_del`. I just need to confirm that my interpretation is correct.
|
| 97/2 |
2026/05/30 16:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:lib/kobject.c Name:kobject_del]
Results: map[SourceCode: 612: /**
613: * kobject_del() - Unlink kobject from hierarchy.
614: * @kobj: object.
615: *
616: * This is the function that should be called to delete an object
617: * successfully added via kobject_add().
618: */
619: void kobject_del(struct kobject *kobj)
620: {
621: struct kobject *parent;
622:
623: if (!kobj)
624: return;
625:
626: parent = kobj->parent;
627: __kobject_del(kobj);
628: kobject_put(parent);
629: }
SourceFile:lib/kobject.c]
|
| 98/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 73522
output: 25
thoughts: 891
Thoughts: **Unraveling the I2C Adapter Deletion Conundrum**
Okay, so `kobject_del` and `device_del` both call their respective `put` functions, meaning all references acquired during their `add` counterparts are dropped during `del`. Excellent. So, a user holding a reference to a *child* device shouldn't prevent the parent device from being released, right?
Thinking this through, if the child device doesn't retain a reference to its parent after `device_del`, then even if a user has a hold on an i2c client, `i2c_del_adapter` *shouldn't* be blocked. Let's confirm with an example. If someone has `/dev/rtc0` open, they're holding a reference to the RTC device. The RTC device holds a reference to its i2c client parent while registered. Unregistering the i2c client unregisters the RTC. `rtc_device_unregister` calls `device_unregister` which calls `device_del`, dropping the client reference. Then, `i2c_unregister_device` does the same for the adapter. The i2c adapter reference count drops to zero, and `i2c_adapter_dev_release` is called, completing the process. So, `i2c_del_adapter` doesn't block with a held child device reference.
But then, why does opening `/dev/i2c-#` *block* `i2c_del_adapter`? Because it's not a *child* device... wait, yes it is! `i2c_dev` is. Okay, let's verify. Checking `i2cdev_attach_adapter`, yes, the `i2c_dev->dev.parent` is set to the adapter's device, confirming `i2c_dev` *is* a child. So when `i2c_del_adapter` is called, it iterates through drivers, notifying `i2c-dev` via `i2cdev_notifier_call`, leading to `i2cdev_detach_adapter`. This calls `put_i2c_dev`, which eventually calls `device_del`, dropping the adapter's reference from the child. But... hang on! `i2cdev_open` explicitly calls `i2c_get_adapter(minor)`! And `i2c_get_adapter` calls `get_device`! Aha! That's it! `i2cdev_open` is *taking an additional, independent* reference to the adapter! This reference is held until `i2cdev_release` is called on close. So, this is why opening `/dev/i2c-#` blocks `i2c_del_adapter`. It directly increments the adapter's reference count.
Now, are there any *other* ways to grab a reference to the i2c adapter directly using `i2c_get_adapter`? That's the key question now. Let's see who else calls `i2c_get_adapter`... that's the next step to fully understand this.
|
| 99/2 |
2026/05/30 16:46 |
tool |
codesearch-find-references |
0m
Args: map[Name:i2c_get_adapter]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:i2c_mux_reg_probe SourceFile:drivers/i2c/muxes/i2c-mux-reg.c SourceLine:178] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:i2cdev_open SourceFile:drivers/i2c/i2c-dev.c SourceLine:604] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mipi_exec_i2c SourceFile:drivers/gpu/drm/i915/display/intel_dsi_vbt.c SourceLine:505]] TruncatedOutput:false]
|
| 100/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 75321
output: 46
thoughts: 362
Thoughts: **Analyzing the i2c Driver's Attack Surface**
Okay, let's break this down from a security perspective. My initial thought process is to trace the user-space attack vectors that can trigger the execution of potentially vulnerable I2C functions, specifically `i2c_mux_reg_probe`, `i2cdev_open`, and `mipi_exec_i2c`. It's pretty clear that, for an unprivileged user, the likely entry point is through `i2cdev_open`. This means they'd have to successfully open `/dev/i2c-#`. Given that these devices are typically restricted to `root` or the `i2c` group, it seems like a normal user can't directly trigger these functions.
But, being the paranoid security-minded person I am, I can't just stop there. I need to exhaust other possibilities. What if a malicious filesystem is mounted and somehow indirectly calls `i2c_get_adapter`? I quickly dismiss that as unlikely. Then, my mind jumps to user namespaces. Could a user namespace provide a loophole? While the user inside a namespace might have `CAP_SYS_ADMIN`, this capability is confined to the namespace itself. They can't magically gain access to `/dev/i2c-#` unless it's available to them in the initial namespace, which it won't be, so user namespaces are a dead end.
I need to think outside the box now. Is there any way an attacker could hold a reference to `adap->dev` without directly calling `i2cdev_open`? My immediate thought is, what about `get_device(&adap->dev)`? Who else calls this? Could it be tied to a sysfs attribute of the I2C adapter? If a user opens a sysfs attribute of the I2C adapter, would it call `get_device`? I started to look for ways to interact with the device through the `sysfs` filesystem. `kernfs_fop_open` doesn't seem to hold a reference, so it doesn't appear promising. But I also need to check `sysfs_kf_read` to see what is invoked there. It calls `ops->show`. So, do any of the `show` functions for the I2C adapter's sysfs attributes involve any blocking operations, or perhaps indirectly keep the reference? Time to examine those attributes closely.
|
| 101/2 |
2026/05/30 16:46 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_adapter_attrs]
Results: map[SourceCode:1374: static struct attribute *i2c_adapter_attrs[] = {
1375: &dev_attr_name.attr,
1376: &dev_attr_new_device.attr,
1377: &dev_attr_delete_device.attr,
1378: NULL
1379: };
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 102/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 76371
output: 49
thoughts: 32
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:
**Working with Device Attributes**
Right, so I'm focusing on the interaction between user-space and the i2c driver. I see that `new_device` and `delete_device` are flagged as writable attributes. That's immediately interesting – it implies a direct mechanism for device control. We're going to need to ensure that the interactions there are safe, with clear input validation and error handling, especially if this opens up security concerns by allowing unauthorized device additions or deletions.
My immediate question is how these attributes are handled internally. Specifically, I need to understand what's going on within `i2c_sysfs_new_device`. I want to dig into the function that's responsible for the new device creation and deletion functionality. I want to see if the implementation conforms to best practices for i2c device registration and unregistration. I'm going to want to check:
* How is the I2C adapter identified? Is it just a device path or something more robust?
* How are device addresses handled? Is there a safe way to prevent address conflicts?
* What happens in case of an error? Do we return a useful error code?
* Is there any race condition when a new device is created while another request is ongoing?
* Is proper locking used to protect the I2C bus?
* Are there any security concerns here? Can a rogue process take over the bus, leading to the creation of unintended devices?
* How does the driver handle multiple devices on the same I2C bus?
* What if a device is added with an invalid address?
I need to understand what safeguards are in place to prevent the user from bricking the system. And how this integration works on the device side. This is going to be my starting point.
|
| 103/2 |
2026/05/30 16:46 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_sysfs_new_device]
Results: map[SourceCode: SourceFile:]
|
| 104/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 76879
output: 38
thoughts: 13
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in device driver development or a related area, and thinking through the implications:
**Diving into `dev_attr_new_device`**
Alright, the next step is definitely to take a closer look at `dev_attr_new_device`. I need to understand what this function is doing. Is it related to the device attribute infrastructure, or something more fundamental? I'm betting it's crucial for properly integrating new devices into the system, likely setting up the necessary attribute structures and linking them correctly. I want to see if it's responsible for the creation of those sysfs entries. Maybe it is also handling the initial population of data. I need to understand the function's signature, its dependencies, and especially, the potential side effects. Is there any memory allocation going on? Does it interact with locking mechanisms? I should check how it interacts with the device model. Depending on the kernel version, its role might be different. I hope the documentation for it is comprehensive. Time to dig in and understand this core piece of the puzzle.
|
| 105/2 |
2026/05/30 16:46 |
tool |
grepper |
0m
Args: map[Expression:new_device PathPrefix:drivers/i2c/i2c-core-base.c]
Results: map[Output:drivers/i2c/i2c-core-base.c=1259=static ssize_t
drivers/i2c/i2c-core-base.c:1260:new_device_store(struct device *dev, struct device_attribute *attr,
drivers/i2c/i2c-core-base.c-1261- const char *buf, size_t count)
--
drivers/i2c/i2c-core-base.c-1272- if (!blank) {
drivers/i2c/i2c-core-base.c:1273: dev_err(dev, "%s: Missing parameters\n", "new_device");
drivers/i2c/i2c-core-base.c-1274- return -EINVAL;
--
drivers/i2c/i2c-core-base.c-1276- if (blank - buf > I2C_NAME_SIZE - 1) {
drivers/i2c/i2c-core-base.c:1277: dev_err(dev, "%s: Invalid device name\n", "new_device");
drivers/i2c/i2c-core-base.c-1278- return -EINVAL;
--
drivers/i2c/i2c-core-base.c-1284- if (res < 1) {
drivers/i2c/i2c-core-base.c:1285: dev_err(dev, "%s: Can't parse I2C address\n", "new_device");
drivers/i2c/i2c-core-base.c-1286- return -EINVAL;
--
drivers/i2c/i2c-core-base.c-1288- if (res > 1 && end != '\n') {
drivers/i2c/i2c-core-base.c:1289: dev_err(dev, "%s: Extra parameters\n", "new_device");
drivers/i2c/i2c-core-base.c-1290- return -EINVAL;
--
drivers/i2c/i2c-core-base.c-1310- mutex_unlock(&adap->userspace_clients_lock);
drivers/i2c/i2c-core-base.c:1311: dev_info(dev, "%s: Instantiated device %s at 0x%02hx\n", "new_device",
drivers/i2c/i2c-core-base.c-1312- info.type, info.addr);
--
drivers/i2c/i2c-core-base.c-1315-}
drivers/i2c/i2c-core-base.c:1316:static DEVICE_ATTR_WO(new_device);
drivers/i2c/i2c-core-base.c-1317-
--
drivers/i2c/i2c-core-base.c-1320- * they got it wrong. This interface can only be used to delete devices
drivers/i2c/i2c-core-base.c:1321: * instantiated by i2c_sysfs_new_device above. This guarantees that we
drivers/i2c/i2c-core-base.c-1322- * don't delete devices to which some kernel code still has references.
--
drivers/i2c/i2c-core-base.c=1374=static struct attribute *i2c_adapter_attrs[] = {
drivers/i2c/i2c-core-base.c-1375- &dev_attr_name.attr,
drivers/i2c/i2c-core-base.c:1376: &dev_attr_new_device.attr,
drivers/i2c/i2c-core-base.c-1377- &dev_attr_delete_device.attr,
]
|
| 106/2 |
2026/05/30 16:46 |
llm |
expert |
0m
Model:
Tokens:
input: 78164
output: 38
thoughts: 40
Thoughts: Okay, here's my take on those thoughts, framed from an expert's perspective:
**Security Considerations for Device Store Access**
Alright, let me break this down. My initial observation is that `new_device_store` and `delete_device_store` are locked down; root privileges are explicitly required for write access. That's good security hygiene, no surprises there. We're effectively preventing unauthorized device additions or deletions at a fundamental level. I'd expect this level of restriction, especially with critical device management functions. Now, the key question arises: what about `name_show`? Is it also a guarded resource? I need to check if there are security implications related to that as well.
|
| 107/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Args: map[Expression:name_show PathPrefix:drivers/i2c/i2c-core-base.c]
Results: map[Output:drivers/i2c/i2c-core-base.c=667=static ssize_t
drivers/i2c/i2c-core-base.c:668:name_show(struct device *dev, struct device_attribute *attr, char *buf)
drivers/i2c/i2c-core-base.c-669-{
]
|
| 108/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 78491
output: 42
thoughts: 9
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the `name_show` Variable**
Alright, let's see what's happening with `name_show`. Given my experience, the name itself suggests this likely represents a data point or a flag related to some sort of display name or representation, specifically a "show". It could be a simple boolean indicating whether a name should be displayed, a string holding the name itself, or potentially something more complex, like an object with display options or even a conditional logic chain determining when and how the name is presented. I need more context to be certain of its specific function. I'll need to check the code that sets and uses this variable; I'll expect to see it used in display logic, perhaps in a templating engine or UI rendering process. The implementation will be crucial for understanding its intended purpose within the system. I anticipate needing to trace its origins and its impact on the output to fully assess its impact on the system.
|
| 109/2 |
2026/05/30 16:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:name_show]
Results: map[SourceCode: 667: static ssize_t
668: name_show(struct device *dev, struct device_attribute *attr, char *buf)
669: {
670: return sprintf(buf, "%s\n", dev->type == &i2c_client_type ?
671: to_i2c_client(dev)->name : to_i2c_adapter(dev)->name);
672: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 110/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 78916
output: 40
thoughts: 154
Thoughts: **Unlocking the I2C Adapter Mystery**
Okay, so I'm facing a situation where standard privilege restrictions seem to be holding firm. It's becoming clear that a straightforward approach – blocking the I2C adapter device – isn't the key. No dice; an unprivileged user can't directly grab a reference to it. That's the expected behavior.
But...wait a minute. What if we don't go through the *direct* route? What if the attack vector lies in manipulating devices dynamically *creating* a new device node? The reproducer cleverly uses `syz_usb_connect`. Excellent – it's going via a USB device, a DVB device to be precise. Vendor ID `0x8086` – Intel, that's clear enough. Now, that product ID, `0x9500`, is where the interesting part lies. I should look for it within the `drivers/media/usb/dvb-usb-v2/ce6230.c` directory, or maybe somewhere similar within the DVB subsystem, as that's the area I should be looking in. That should offer the clues I need.
|
| 111/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Error:
Args: map[Expression:0x9500 PathPrefix:drivers/media/usb/dvb-usb-v2/]
Results:
|
| 112/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 79376
output: 32
thoughts: 21
Thoughts: Here's my thought process, as it is:
**Checking `0x9500` in `drivers/media/usb/`**
Okay, so I need to find something in the `drivers/media/usb/` directory. The specific concern, judging from the prompt, is likely related to a device ID, `0x9500`. This suggests that I'm looking for a driver that supports a particular USB device. My initial thought is to dive right into the code and see if there are any obvious matches for this ID within that directory. I'll probably be looking for a device table or similar structure within the driver files. It could be in a more generic USB driver file, or it might be specific to a camera, tuner, or other media-related device. The context strongly implies a debugging task, a driver update, or possibly integrating a new device. I'll be looking at the vendor and product ID specifically. I'll likely use `grep`, and `find` to start to narrow the search.
|
| 113/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Error:
Args: map[Expression:0x9500 PathPrefix:drivers/media/usb/]
Results:
|
| 114/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 79662
output: 32
thoughts: 35
Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested:
**Navigating the Digital Landscape**
Alright, that little nudge from `0x8086` – that's a classic Intel product identifier, right? It's like a familiar name popping up in a conversation. So, I need to zoom in on what specifically they're referring to. Given the context, my mind immediately jumps to networking, and specifically USB networking devices. Let's see, now... the next step is to drill down and check the `drivers/net/usb/` directory. That seems like the logical place to start poking around in the code for `0x9500`. It could be related to a specific USB network adapter that I need to investigate. I'll need to see if I can find some reference to it in the code there.
|
| 115/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Args: map[Expression:0x9500 PathPrefix:drivers/net/usb/]
Results: map[Output:drivers/net/usb/smsc95xx.c-36-#define MAX_SINGLE_PACKET_SIZE (2048)
drivers/net/usb/smsc95xx.c:37:#define LAN95XX_EEPROM_MAGIC (0x9500)
drivers/net/usb/smsc95xx.c-38-#define EEPROM_MAC_OFFSET (0x01)
--
drivers/net/usb/smsc95xx.c=2018=static const struct usb_device_id products[] = {
--
drivers/net/usb/smsc95xx.c-2020- /* SMSC9500 USB Ethernet Device */
drivers/net/usb/smsc95xx.c:2021: USB_DEVICE(0x0424, 0x9500),
drivers/net/usb/smsc95xx.c-2022- .driver_info = (unsigned long) &smsc95xx_info,
--
drivers/net/usb/smsc95xx.h-42-#define ID_REV_CHIP_REV_MASK_ (0x0000FFFF)
drivers/net/usb/smsc95xx.h:43:#define ID_REV_CHIP_ID_9500_ (0x9500)
drivers/net/usb/smsc95xx.h-44-#define ID_REV_CHIP_ID_9500A_ (0x9E00)
]
|
| 116/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 80306
output: 40
thoughts: 191
Thoughts: **Diving into the DVB USB Driver Issue**
Okay, so `0x0424` is definitely an SMSC device, but the reported reproducer specifies `idVendor=0x8086` and `idProduct=0x9500`. That doesn't immediately compute. Wait, the bug report specifically mentions `dvb_usbv2_probe`! Alright, that clicks – this is likely a DVB device, or at least interacting with the DVB subsystem. The stack trace, with `i2c_del_adapter` and the `dvb_usbv2_probe` call, strongly suggests that. Time to go hunting. I need to examine the `drivers/media/usb/dvb-usb-v2/` directory and see how it handles vendor ID `0x8086`. Let's see what's going on in those probe functions.
|
| 117/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Error:
Args: map[Expression:0x8086 PathPrefix:drivers/media/usb/dvb-usb-v2/]
Results:
|
| 118/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 80747
output: 38
thoughts: 41
Thoughts: Okay, here's my interpretation of that thought process, as if I were having it:
**Digging into Device IDs**
Alright, that device ID, `0x8086`, is screaming Intel to me. Let's see if I'm right, and also, let's figure out what drivers we'd need to go into. When dealing with obscure hardware, I often jump straight to the source, and in this case, the `drivers/media/usb/dvb-usb-v2/` directory feels like a likely candidate. It's the go-to place for USB DVB devices, and I expect I'll find a mention of `8086` buried in the code if it's relevant to the project I'm working on. The goal is to quickly find and confirm whether `0x8086` is used, and if so, how.
|
| 119/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Error:
Args: map[Expression:8086 PathPrefix:drivers/media/usb/dvb-usb-v2/]
Results:
|
| 120/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 81034
output: 37
thoughts: 166
Thoughts: **Debugging a USB Device Probe**
Okay, hold on a second. I need to retrace my steps. How exactly did `dvb_usbv2_probe` get triggered for this device, `0x8086:0x9500`? I can't quite see the connection immediately. Is this perhaps a dynamic ID assignment that I'm overlooking? Or is the driver being invoked based on something else entirely, like the interface class? The reproducer sets `bInterfaceClass` to 0, which doesn't seem directly relevant, but I should probably keep it in mind. Let me look deeper. Wait, the `bDeviceClass` is set to `0x8e`. Hmmm. Isn't that... Oh shoot! `0x8e` is not `USB_CLASS_VIDEO`, which is `0x0e` (14 decimal). So then, what *is* `0x8e`? I need to figure this out. I'm going to have to dive into the `usb_device_id` tables within `drivers/media/usb/dvb-usb-v2/` to understand why this probe is happening.
|
| 121/2 |
2026/05/30 16:47 |
tool |
grepper |
0m
Args: map[Expression:USB_DEVICE PathPrefix:drivers/media/usb/dvb-usb-v2/]
Results: map[Output:drivers/media/usb/dvb-usb-v2/af9015.c=1456=static const struct usb_device_id af9015_id_table[] = {
drivers/media/usb/dvb-usb-v2/af9015.c:1457: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9015,
drivers/media/usb/dvb-usb-v2/af9015.c-1458- &af9015_props, "Afatech AF9015 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1459: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9016,
drivers/media/usb/dvb-usb-v2/af9015.c-1460- &af9015_props, "Afatech AF9015 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1461: { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_GOLD,
drivers/media/usb/dvb-usb-v2/af9015.c-1462- &af9015_props, "Leadtek WinFast DTV Dongle Gold", RC_MAP_LEADTEK_Y04G0051) },
drivers/media/usb/dvb-usb-v2/af9015.c:1463: { DVB_USB_DEVICE(USB_VID_PINNACLE, USB_PID_PINNACLE_PCTV71E,
drivers/media/usb/dvb-usb-v2/af9015.c-1464- &af9015_props, "Pinnacle PCTV 71e", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1465: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U,
drivers/media/usb/dvb-usb-v2/af9015.c-1466- &af9015_props, "KWorld PlusTV Dual DVB-T Stick (DVB-T 399U)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1467: { DVB_USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TINYTWIN,
drivers/media/usb/dvb-usb-v2/af9015.c-1468- &af9015_props, "DigitalNow TinyTwin", RC_MAP_AZUREWAVE_AD_TU700) },
drivers/media/usb/dvb-usb-v2/af9015.c:1469: { DVB_USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_AZUREWAVE_AD_TU700,
drivers/media/usb/dvb-usb-v2/af9015.c-1470- &af9015_props, "TwinHan AzureWave AD-TU700(704J)", RC_MAP_AZUREWAVE_AD_TU700) },
drivers/media/usb/dvb-usb-v2/af9015.c:1471: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2,
drivers/media/usb/dvb-usb-v2/af9015.c-1472- &af9015_props, "TerraTec Cinergy T USB XE", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1473: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_2T,
drivers/media/usb/dvb-usb-v2/af9015.c-1474- &af9015_props, "KWorld PlusTV Dual DVB-T PCI (DVB-T PC160-2T)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1475: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X,
drivers/media/usb/dvb-usb-v2/af9015.c-1476- &af9015_props, "AVerMedia AVerTV DVB-T Volar X", RC_MAP_AVERMEDIA_M135A) },
drivers/media/usb/dvb-usb-v2/af9015.c:1477: { DVB_USB_DEVICE(USB_VID_XTENSIONS, USB_PID_XTENSIONS_XD_380,
drivers/media/usb/dvb-usb-v2/af9015.c-1478- &af9015_props, "Xtensions XD-380", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1479: { DVB_USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGIVOX_DUO,
drivers/media/usb/dvb-usb-v2/af9015.c-1480- &af9015_props, "MSI DIGIVOX Duo", RC_MAP_MSI_DIGIVOX_III) },
drivers/media/usb/dvb-usb-v2/af9015.c:1481: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X_2,
drivers/media/usb/dvb-usb-v2/af9015.c-1482- &af9015_props, "Fujitsu-Siemens Slim Mobile USB DVB-T", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1483: { DVB_USB_DEVICE(USB_VID_TELESTAR, USB_PID_TELESTAR_STARSTICK_2,
drivers/media/usb/dvb-usb-v2/af9015.c-1484- &af9015_props, "Telestar Starstick 2", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1485: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A309,
drivers/media/usb/dvb-usb-v2/af9015.c-1486- &af9015_props, "AVerMedia A309", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1487: { DVB_USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGI_VOX_MINI_III,
drivers/media/usb/dvb-usb-v2/af9015.c-1488- &af9015_props, "MSI Digi VOX mini III", RC_MAP_MSI_DIGIVOX_III) },
drivers/media/usb/dvb-usb-v2/af9015.c:1489: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U,
drivers/media/usb/dvb-usb-v2/af9015.c-1490- &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1491: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_2,
drivers/media/usb/dvb-usb-v2/af9015.c-1492- &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1493: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_3,
drivers/media/usb/dvb-usb-v2/af9015.c-1494- &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1495: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_TREKSTOR_DVBT,
drivers/media/usb/dvb-usb-v2/af9015.c-1496- &af9015_props, "TrekStor DVB-T USB Stick", RC_MAP_TREKSTOR) },
drivers/media/usb/dvb-usb-v2/af9015.c:1497: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850,
drivers/media/usb/dvb-usb-v2/af9015.c-1498- &af9015_props, "AverMedia AVerTV Volar Black HD (A850)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1499: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A805,
drivers/media/usb/dvb-usb-v2/af9015.c-1500- &af9015_props, "AverMedia AVerTV Volar GPS 805 (A805)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1501: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CONCEPTRONIC_CTVDIGRCU,
drivers/media/usb/dvb-usb-v2/af9015.c-1502- &af9015_props, "Conceptronic USB2.0 DVB-T CTVDIGRCU V3.0", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1503: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_MC810,
drivers/media/usb/dvb-usb-v2/af9015.c-1504- &af9015_props, "KWorld Digital MC-810", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1505: { DVB_USB_DEVICE(USB_VID_KYE, USB_PID_GENIUS_TVGO_DVB_T03,
drivers/media/usb/dvb-usb-v2/af9015.c-1506- &af9015_props, "Genius TVGo DVB-T03", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1507: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U_2,
drivers/media/usb/dvb-usb-v2/af9015.c-1508- &af9015_props, "KWorld PlusTV Dual DVB-T Stick (DVB-T 399U)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1509: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_T,
drivers/media/usb/dvb-usb-v2/af9015.c-1510- &af9015_props, "KWorld PlusTV DVB-T PCI Pro Card (DVB-T PC160-T)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1511: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV20,
drivers/media/usb/dvb-usb-v2/af9015.c-1512- &af9015_props, "Sveon STV20 Tuner USB DVB-T HDTV", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1513: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_TINYTWIN_2,
drivers/media/usb/dvb-usb-v2/af9015.c-1514- &af9015_props, "DigitalNow TinyTwin v2", RC_MAP_DIGITALNOW_TINYTWIN) },
drivers/media/usb/dvb-usb-v2/af9015.c:1515: { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV2000DS,
drivers/media/usb/dvb-usb-v2/af9015.c-1516- &af9015_props, "Leadtek WinFast DTV2000DS", RC_MAP_LEADTEK_Y04G0051) },
drivers/media/usb/dvb-usb-v2/af9015.c:1517: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB383_T,
drivers/media/usb/dvb-usb-v2/af9015.c-1518- &af9015_props, "KWorld USB DVB-T Stick Mobile (UB383-T)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1519: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_4,
drivers/media/usb/dvb-usb-v2/af9015.c-1520- &af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1521: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A815M,
drivers/media/usb/dvb-usb-v2/af9015.c-1522- &af9015_props, "AverMedia AVerTV Volar M (A815Mac)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1523: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_RC,
drivers/media/usb/dvb-usb-v2/af9015.c-1524- &af9015_props, "TerraTec Cinergy T Stick RC", RC_MAP_TERRATEC_SLIM_2) },
drivers/media/usb/dvb-usb-v2/af9015.c-1525- /* XXX: that same ID [0ccd:0099] is used by af9035 driver too */
drivers/media/usb/dvb-usb-v2/af9015.c:1526: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC,
drivers/media/usb/dvb-usb-v2/af9015.c-1527- &af9015_props, "TerraTec Cinergy T Stick Dual RC", RC_MAP_TERRATEC_SLIM) },
drivers/media/usb/dvb-usb-v2/af9015.c:1528: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850T,
drivers/media/usb/dvb-usb-v2/af9015.c-1529- &af9015_props, "AverMedia AVerTV Red HD+ (A850T)", NULL) },
drivers/media/usb/dvb-usb-v2/af9015.c:1530: { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_TINYTWIN_3,
drivers/media/usb/dvb-usb-v2/af9015.c-1531- &af9015_props, "DigitalNow TinyTwin v3", RC_MAP_DIGITALNOW_TINYTWIN) },
drivers/media/usb/dvb-usb-v2/af9015.c:1532: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22,
drivers/media/usb/dvb-usb-v2/af9015.c-1533- &af9015_props, "Sveon STV22 Dual USB DVB-T Tuner HDTV", RC_MAP_MSI_DIGIVOX_III) },
--
drivers/media/usb/dvb-usb-v2/af9035.c=2095=static const struct usb_device_id af9035_id_table[] = {
drivers/media/usb/dvb-usb-v2/af9035.c-2096- /* AF9035 devices */
drivers/media/usb/dvb-usb-v2/af9035.c:2097: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_9035,
drivers/media/usb/dvb-usb-v2/af9035.c-2098- &af9035_props, "Afatech AF9035 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2099: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1000,
drivers/media/usb/dvb-usb-v2/af9035.c-2100- &af9035_props, "Afatech AF9035 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2101: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1001,
drivers/media/usb/dvb-usb-v2/af9035.c-2102- &af9035_props, "Afatech AF9035 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2103: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1002,
drivers/media/usb/dvb-usb-v2/af9035.c-2104- &af9035_props, "Afatech AF9035 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2105: { DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1003,
drivers/media/usb/dvb-usb-v2/af9035.c-2106- &af9035_props, "Afatech AF9035 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2107: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK,
drivers/media/usb/dvb-usb-v2/af9035.c-2108- &af9035_props, "TerraTec Cinergy T Stick", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2109: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835,
drivers/media/usb/dvb-usb-v2/af9035.c-2110- &af9035_props, "AVerMedia AVerTV Volar HD/PRO (A835)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2111: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_B835,
drivers/media/usb/dvb-usb-v2/af9035.c-2112- &af9035_props, "AVerMedia AVerTV Volar HD/PRO (A835)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2113: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_1867,
drivers/media/usb/dvb-usb-v2/af9035.c-2114- &af9035_props, "AVerMedia HD Volar (A867)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2115: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A867,
drivers/media/usb/dvb-usb-v2/af9035.c-2116- &af9035_props, "AVerMedia HD Volar (A867)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2117: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TWINSTAR,
drivers/media/usb/dvb-usb-v2/af9035.c-2118- &af9035_props, "AVerMedia Twinstar (A825)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2119: { DVB_USB_DEVICE(USB_VID_ASUS, USB_PID_ASUS_U3100MINI_PLUS,
drivers/media/usb/dvb-usb-v2/af9035.c-2120- &af9035_props, "Asus U3100Mini Plus", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2121: { DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00aa,
drivers/media/usb/dvb-usb-v2/af9035.c-2122- &af9035_props, "TerraTec Cinergy T Stick (rev. 2)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2123: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, 0x0337,
drivers/media/usb/dvb-usb-v2/af9035.c-2124- &af9035_props, "AVerMedia HD Volar (A867)", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2125: { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_EVOLVEO_XTRATV_STICK,
drivers/media/usb/dvb-usb-v2/af9035.c-2126- &af9035_props, "EVOLVEO XtraTV stick", NULL) },
--
drivers/media/usb/dvb-usb-v2/af9035.c-2128- /* IT9135 devices */
drivers/media/usb/dvb-usb-v2/af9035.c:2129: { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135,
drivers/media/usb/dvb-usb-v2/af9035.c-2130- &af9035_props, "ITE 9135 Generic", RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c:2131: { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9005,
drivers/media/usb/dvb-usb-v2/af9035.c-2132- &af9035_props, "ITE 9135(9005) Generic", RC_MAP_IT913X_V2) },
drivers/media/usb/dvb-usb-v2/af9035.c:2133: { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9006,
drivers/media/usb/dvb-usb-v2/af9035.c-2134- &af9035_props, "ITE 9135(9006) Generic", RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c:2135: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_1835,
drivers/media/usb/dvb-usb-v2/af9035.c-2136- &af9035_props, "Avermedia A835B(1835)", RC_MAP_IT913X_V2) },
drivers/media/usb/dvb-usb-v2/af9035.c:2137: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_2835,
drivers/media/usb/dvb-usb-v2/af9035.c-2138- &af9035_props, "Avermedia A835B(2835)", RC_MAP_IT913X_V2) },
drivers/media/usb/dvb-usb-v2/af9035.c:2139: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_3835,
drivers/media/usb/dvb-usb-v2/af9035.c-2140- &af9035_props, "Avermedia A835B(3835)", RC_MAP_IT913X_V2) },
drivers/media/usb/dvb-usb-v2/af9035.c:2141: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_4835,
drivers/media/usb/dvb-usb-v2/af9035.c-2142- &af9035_props, "Avermedia A835B(4835)", RC_MAP_IT913X_V2) },
drivers/media/usb/dvb-usb-v2/af9035.c:2143: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TD110,
drivers/media/usb/dvb-usb-v2/af9035.c-2144- &af9035_props, "Avermedia AverTV Volar HD 2 (TD110)", RC_MAP_AVERMEDIA_RM_KS) },
drivers/media/usb/dvb-usb-v2/af9035.c:2145: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_H335,
drivers/media/usb/dvb-usb-v2/af9035.c-2146- &af9035_props, "Avermedia H335", RC_MAP_IT913X_V2) },
drivers/media/usb/dvb-usb-v2/af9035.c:2147: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB499_2T_T09,
drivers/media/usb/dvb-usb-v2/af9035.c-2148- &af9035_props, "Kworld UB499-2T T09", RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c:2149: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22_IT9137,
drivers/media/usb/dvb-usb-v2/af9035.c-2150- &af9035_props, "Sveon STV22 Dual DVB-T HDTV",
drivers/media/usb/dvb-usb-v2/af9035.c-2151- RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c:2152: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CTVDIGDUAL_V2,
drivers/media/usb/dvb-usb-v2/af9035.c-2153- &af9035_props, "Digital Dual TV Receiver CTVDIGDUAL_V2",
drivers/media/usb/dvb-usb-v2/af9035.c-2154- RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c:2155: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_T1,
drivers/media/usb/dvb-usb-v2/af9035.c-2156- &af9035_props, "TerraTec T1", RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c-2157- /* XXX: that same ID [0ccd:0099] is used by af9015 driver too */
drivers/media/usb/dvb-usb-v2/af9035.c:2158: { DVB_USB_DEVICE(USB_VID_TERRATEC, 0x0099,
drivers/media/usb/dvb-usb-v2/af9035.c-2159- &af9035_props, "TerraTec Cinergy T Stick Dual RC (rev. 2)",
drivers/media/usb/dvb-usb-v2/af9035.c-2160- NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2161: { DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6a05,
drivers/media/usb/dvb-usb-v2/af9035.c-2162- &af9035_props, "Leadtek WinFast DTV Dongle Dual", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2163: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xf900,
drivers/media/usb/dvb-usb-v2/af9035.c-2164- &af9035_props, "Hauppauge WinTV-MiniStick 2", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2165: { DVB_USB_DEVICE(USB_VID_PCTV, USB_PID_PCTV_78E,
drivers/media/usb/dvb-usb-v2/af9035.c-2166- &af9035_props, "PCTV AndroiDTV (78e)", RC_MAP_IT913X_V1) },
drivers/media/usb/dvb-usb-v2/af9035.c:2167: { DVB_USB_DEVICE(USB_VID_PCTV, USB_PID_PCTV_79E,
drivers/media/usb/dvb-usb-v2/af9035.c-2168- &af9035_props, "PCTV microStick (79e)", RC_MAP_IT913X_V2) },
--
drivers/media/usb/dvb-usb-v2/af9035.c-2170- /* IT930x devices */
drivers/media/usb/dvb-usb-v2/af9035.c:2171: { DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9303,
drivers/media/usb/dvb-usb-v2/af9035.c-2172- &it930x_props, "ITE 9303 Generic", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2173: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TD310,
drivers/media/usb/dvb-usb-v2/af9035.c-2174- &it930x_props, "AVerMedia TD310 DVB-T2", RC_MAP_AVERMEDIA_RM_KS) },
drivers/media/usb/dvb-usb-v2/af9035.c:2175: { DVB_USB_DEVICE(USB_VID_DEXATEK, 0x0100,
drivers/media/usb/dvb-usb-v2/af9035.c-2176- &it930x_props, "Logilink VG0022A", NULL) },
drivers/media/usb/dvb-usb-v2/af9035.c:2177: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_TC2_STICK,
drivers/media/usb/dvb-usb-v2/af9035.c-2178- &it930x_props, "TerraTec Cinergy TC2 Stick", NULL) },
--
drivers/media/usb/dvb-usb-v2/anysee.c=1380=static const struct usb_device_id anysee_id_table[] = {
drivers/media/usb/dvb-usb-v2/anysee.c:1381: { DVB_USB_DEVICE(USB_VID_CYPRESS, USB_PID_ANYSEE,
drivers/media/usb/dvb-usb-v2/anysee.c-1382- &anysee_props, "Anysee", RC_MAP_ANYSEE) },
drivers/media/usb/dvb-usb-v2/anysee.c:1383: { DVB_USB_DEVICE(USB_VID_AMT, USB_PID_ANYSEE,
drivers/media/usb/dvb-usb-v2/anysee.c-1384- &anysee_props, "Anysee", RC_MAP_ANYSEE) },
--
drivers/media/usb/dvb-usb-v2/au6610.c=176=static const struct usb_device_id au6610_id_table[] = {
drivers/media/usb/dvb-usb-v2/au6610.c:177: { DVB_USB_DEVICE(USB_VID_ALCOR_MICRO, USB_PID_SIGMATEK_DVB_110,
drivers/media/usb/dvb-usb-v2/au6610.c-178- &au6610_props, "Sigmatek DVB-110", NULL) },
--
drivers/media/usb/dvb-usb-v2/az6007.c=938=static const struct usb_device_id az6007_usb_table[] = {
drivers/media/usb/dvb-usb-v2/az6007.c:939: {DVB_USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_6007,
drivers/media/usb/dvb-usb-v2/az6007.c-940- &az6007_props, "Azurewave 6007", RC_MAP_EMPTY)},
drivers/media/usb/dvb-usb-v2/az6007.c:941: {DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7,
drivers/media/usb/dvb-usb-v2/az6007.c-942- &az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)},
drivers/media/usb/dvb-usb-v2/az6007.c:943: {DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7_2,
drivers/media/usb/dvb-usb-v2/az6007.c-944- &az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)},
drivers/media/usb/dvb-usb-v2/az6007.c:945: {DVB_USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_CABLESTAR_HDCI,
drivers/media/usb/dvb-usb-v2/az6007.c-946- &az6007_cablestar_hdci_props, "Technisat CableStar Combo HD CI", RC_MAP_EMPTY)},
--
drivers/media/usb/dvb-usb-v2/ce6230.c=261=static const struct usb_device_id ce6230_id_table[] = {
drivers/media/usb/dvb-usb-v2/ce6230.c:262: { DVB_USB_DEVICE(USB_VID_INTEL, USB_PID_INTEL_CE9500,
drivers/media/usb/dvb-usb-v2/ce6230.c-263- &ce6230_props, "Intel CE9500 reference design", NULL) },
drivers/media/usb/dvb-usb-v2/ce6230.c:264: { DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A310,
drivers/media/usb/dvb-usb-v2/ce6230.c-265- &ce6230_props, "AVerMedia A310 USB 2.0 DVB-T tuner", NULL) },
--
drivers/media/usb/dvb-usb-v2/dvb_usb.h-88-
drivers/media/usb/dvb-usb-v2/dvb_usb.h:89:#define DVB_USB_DEVICE(vend, prod, props_, name_, rc) \
drivers/media/usb/dvb-usb-v2/dvb_usb.h:90: .match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
drivers/media/usb/dvb-usb-v2/dvb_usb.h-91- .idVendor = (vend), \
--
drivers/media/usb/dvb-usb-v2/dvbsky.c=759=static const struct usb_device_id dvbsky_id_table[] = {
drivers/media/usb/dvb-usb-v2/dvbsky.c:760: { DVB_USB_DEVICE(0x0572, 0x6831,
drivers/media/usb/dvb-usb-v2/dvbsky.c-761- &dvbsky_s960_props, "DVBSky S960/S860", RC_MAP_DVBSKY) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:762: { DVB_USB_DEVICE(0x0572, 0x960c,
drivers/media/usb/dvb-usb-v2/dvbsky.c-763- &dvbsky_s960c_props, "DVBSky S960CI", RC_MAP_DVBSKY) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:764: { DVB_USB_DEVICE(0x0572, 0x680c,
drivers/media/usb/dvb-usb-v2/dvbsky.c-765- &dvbsky_t680c_props, "DVBSky T680CI", RC_MAP_DVBSKY) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:766: { DVB_USB_DEVICE(0x0572, 0x0320,
drivers/media/usb/dvb-usb-v2/dvbsky.c-767- &dvbsky_t330_props, "DVBSky T330", RC_MAP_DVBSKY) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:768: { DVB_USB_DEVICE(USB_VID_TECHNOTREND,
drivers/media/usb/dvb-usb-v2/dvbsky.c-769- USB_PID_TECHNOTREND_TVSTICK_CT2_4400,
--
drivers/media/usb/dvb-usb-v2/dvbsky.c-771- RC_MAP_TT_1500) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:772: { DVB_USB_DEVICE(USB_VID_TECHNOTREND,
drivers/media/usb/dvb-usb-v2/dvbsky.c-773- USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI,
--
drivers/media/usb/dvb-usb-v2/dvbsky.c-775- RC_MAP_TT_1500) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:776: { DVB_USB_DEVICE(USB_VID_TECHNOTREND,
drivers/media/usb/dvb-usb-v2/dvbsky.c-777- USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI_2,
--
drivers/media/usb/dvb-usb-v2/dvbsky.c-779- RC_MAP_TT_1500) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:780: { DVB_USB_DEVICE(USB_VID_TECHNOTREND,
drivers/media/usb/dvb-usb-v2/dvbsky.c-781- USB_PID_TECHNOTREND_CONNECT_S2_4650_CI,
--
drivers/media/usb/dvb-usb-v2/dvbsky.c-783- RC_MAP_TT_1500) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:784: { DVB_USB_DEVICE(USB_VID_TERRATEC,
drivers/media/usb/dvb-usb-v2/dvbsky.c-785- USB_PID_TERRATEC_H7_3,
--
drivers/media/usb/dvb-usb-v2/dvbsky.c-787- RC_MAP_TT_1500) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:788: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S2_R4,
drivers/media/usb/dvb-usb-v2/dvbsky.c-789- &dvbsky_s960_props, "Terratec Cinergy S2 Rev.4",
drivers/media/usb/dvb-usb-v2/dvbsky.c-790- RC_MAP_DVBSKY) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:791: { DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230,
drivers/media/usb/dvb-usb-v2/dvbsky.c-792- &mygica_t230c_props, "MyGica Mini DVB-(T/T2/C) USB Stick T230",
drivers/media/usb/dvb-usb-v2/dvbsky.c-793- RC_MAP_TOTAL_MEDIA_IN_HAND_02) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:794: { DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230C,
drivers/media/usb/dvb-usb-v2/dvbsky.c-795- &mygica_t230c_props, "MyGica Mini DVB-(T/T2/C) USB Stick T230C",
drivers/media/usb/dvb-usb-v2/dvbsky.c-796- RC_MAP_TOTAL_MEDIA_IN_HAND_02) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:797: { DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230C_LITE,
drivers/media/usb/dvb-usb-v2/dvbsky.c-798- &mygica_t230c_props, "MyGica Mini DVB-(T/T2/C) USB Stick T230C Lite",
drivers/media/usb/dvb-usb-v2/dvbsky.c-799- NULL) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:800: { DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230C2,
drivers/media/usb/dvb-usb-v2/dvbsky.c-801- &mygica_t230c_props, "MyGica Mini DVB-(T/T2/C) USB Stick T230C v2",
drivers/media/usb/dvb-usb-v2/dvbsky.c-802- RC_MAP_TOTAL_MEDIA_IN_HAND_02) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:803: { DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230C2_LITE,
drivers/media/usb/dvb-usb-v2/dvbsky.c-804- &mygica_t230c_props, "MyGica Mini DVB-(T/T2/C) USB Stick T230C v2 Lite",
drivers/media/usb/dvb-usb-v2/dvbsky.c-805- NULL) },
drivers/media/usb/dvb-usb-v2/dvbsky.c:806: { DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230A,
drivers/media/usb/dvb-usb-v2/dvbsky.c-807- &mygica_t230c_props, "MyGica Mini DVB-(T/T2/C) USB Stick T230A",
--
drivers/media/usb/dvb-usb-v2/ec168.c=347=static const struct usb_device_id ec168_id[] = {
drivers/media/usb/dvb-usb-v2/ec168.c:348: { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168,
drivers/media/usb/dvb-usb-v2/ec168.c-349- &ec168_props, "E3C EC168 reference design", NULL)},
drivers/media/usb/dvb-usb-v2/ec168.c:350: { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_2,
drivers/media/usb/dvb-usb-v2/ec168.c-351- &ec168_props, "E3C EC168 reference design", NULL)},
drivers/media/usb/dvb-usb-v2/ec168.c:352: { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_3,
drivers/media/usb/dvb-usb-v2/ec168.c-353- &ec168_props, "E3C EC168 reference design", NULL)},
drivers/media/usb/dvb-usb-v2/ec168.c:354: { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_4,
drivers/media/usb/dvb-usb-v2/ec168.c-355- &ec168_props, "E3C EC168 reference design", NULL)},
drivers/media/usb/dvb-usb-v2/ec168.c:356: { DVB_USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_5,
drivers/media/usb/dvb-usb-v2/ec168.c-357- &ec168_props, "E3C EC168 reference design", NULL)},
--
drivers/media/usb/dvb-usb-v2/gl861.c=550=static const struct usb_device_id gl861_id_table[] = {
drivers/media/usb/dvb-usb-v2/gl861.c:551: { DVB_USB_DEVICE(USB_VID_MSI, USB_PID_MSI_MEGASKY580_55801,
drivers/media/usb/dvb-usb-v2/gl861.c-552- &gl861_props, "MSI Mega Sky 55801 DVB-T USB2.0", NULL) },
drivers/media/usb/dvb-usb-v2/gl861.c:553: { DVB_USB_DEVICE(USB_VID_ALINK, USB_PID_ALINK_DTU,
drivers/media/usb/dvb-usb-v2/gl861.c-554- &gl861_props, "A-LINK DTU DVB-T USB2.0", NULL) },
drivers/media/usb/dvb-usb-v2/gl861.c:555: { DVB_USB_DEVICE(USB_VID_774, USB_PID_FRIIO_WHITE,
drivers/media/usb/dvb-usb-v2/gl861.c-556- &friio_props, "774 Friio White ISDB-T USB2.0", NULL) },
--
drivers/media/usb/dvb-usb-v2/lmedm04.c=1241=static const struct usb_device_id lme2510_id_table[] = {
drivers/media/usb/dvb-usb-v2/lmedm04.c:1242: { DVB_USB_DEVICE(0x3344, 0x1122, &lme2510_props,
drivers/media/usb/dvb-usb-v2/lmedm04.c-1243- "DM04_LME2510_DVB-S", RC_MAP_LME2510) },
drivers/media/usb/dvb-usb-v2/lmedm04.c:1244: { DVB_USB_DEVICE(0x3344, 0x1120, &lme2510_props,
drivers/media/usb/dvb-usb-v2/lmedm04.c-1245- "DM04_LME2510C_DVB-S", RC_MAP_LME2510) },
drivers/media/usb/dvb-usb-v2/lmedm04.c:1246: { DVB_USB_DEVICE(0x3344, 0x22f0, &lme2510_props,
drivers/media/usb/dvb-usb-v2/lmedm04.c-1247- "DM04_LME2510C_DVB-S RS2000", RC_MAP_LME2510) },
--
drivers/media/usb/dvb-usb-v2/mxl111sf.c=1411=static const struct usb_device_id mxl111sf_id_table[] = {
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1412: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc600, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1413: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc601, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1414: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc602, &mxl111sf_props_mh, "HCW 126xxx", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1415: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc603, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1416: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc604, &mxl111sf_props_dvbt, "Hauppauge 126xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1417: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc609, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1418: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60a, &mxl111sf_props_mh, "HCW 126xxx", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1419: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60b, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1420: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60c, &mxl111sf_props_dvbt, "Hauppauge 126xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1421: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc653, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1422: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc65b, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1423: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb700, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1424: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb701, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1425: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb702, &mxl111sf_props_mh, "HCW 117xxx", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1426: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb703, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1427: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb704, &mxl111sf_props_dvbt, "Hauppauge 117xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1428: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb753, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1429: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb763, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1430: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb764, &mxl111sf_props_dvbt, "Hauppauge 117xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1431: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd853, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1432: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd854, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1433: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd863, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1434: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd864, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1435: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d3, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1436: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d4, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1437: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e3, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1438: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e4, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1439: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8ff, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1440: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc612, &mxl111sf_props_mercury_mh, "Hauppauge 126xxx", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1441: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc613, &mxl111sf_props_mercury, "Hauppauge WinTV-Aero-M", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1442: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61a, &mxl111sf_props_mercury_mh, "Hauppauge 126xxx", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1443: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61b, &mxl111sf_props_mercury, "Hauppauge WinTV-Aero-M", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1444: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb757, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c:1445: { DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb767, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
drivers/media/usb/dvb-usb-v2/mxl111sf.c-1446- { }
--
drivers/media/usb/dvb-usb-v2/rtl28xxu.c=1915=static const struct usb_device_id rtl28xxu_id_table[] = {
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1916- /* RTL2831U devices: */
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1917: { DVB_USB_DEVICE(USB_VID_REALTEK, USB_PID_REALTEK_RTL2831U,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1918- &rtl28xxu_props, "Realtek RTL2831U reference design", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1919: { DVB_USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1920- &rtl28xxu_props, "Freecom USB2.0 DVB-T", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1921: { DVB_USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT_2,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1922- &rtl28xxu_props, "Freecom USB2.0 DVB-T", NULL) },
--
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1924- /* RTL2832U devices: */
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1925: { DVB_USB_DEVICE(USB_VID_REALTEK, 0x2832,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1926- &rtl28xxu_props, "Realtek RTL2832U reference design", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1927: { DVB_USB_DEVICE(USB_VID_REALTEK, 0x2838,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1928- &rtl28xxu_props, "Realtek RTL2832U reference design", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1929: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1930- &rtl28xxu_props, "TerraTec Cinergy T Stick Black", RC_MAP_TERRATEC_SLIM) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1931: { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_DELOCK_USB2_DVBT,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1932- &rtl28xxu_props, "G-Tek Electronics Group Lifeview LV5TDLX DVB-T", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1933: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1934- &rtl28xxu_props, "TerraTec NOXON DAB Stick", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1935: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK_REV2,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1936- &rtl28xxu_props, "TerraTec NOXON DAB Stick (rev 2)", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1937: { DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK_REV3,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1938- &rtl28xxu_props, "TerraTec NOXON DAB Stick (rev 3)", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1939: { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_TREKSTOR_TERRES_2_0,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1940- &rtl28xxu_props, "Trekstor DVB-T Stick Terres 2.0", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1941: { DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1101,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1942- &rtl28xxu_props, "Dexatek DK DVB-T Dongle", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1943: { DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6680,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1944- &rtl28xxu_props, "DigitalNow Quad DVB-T Receiver", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1945: { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_MINID,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1946- &rtl28xxu_props, "Leadtek Winfast DTV Dongle Mini D", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1947: { DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV2000DS_PLUS,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1948- &rtl28xxu_props, "Leadtek WinFast DTV2000DS Plus", RC_MAP_LEADTEK_Y04G0051) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1949: { DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00d3,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1950- &rtl28xxu_props, "TerraTec Cinergy T Stick RC (Rev. 3)", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1951: { DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1102,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1952- &rtl28xxu_props, "Dexatek DK mini DVB-T Dongle", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1953: { DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00d7,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1954- &rtl28xxu_props, "TerraTec Cinergy T Stick+", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1955: { DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd3a8,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1956- &rtl28xxu_props, "ASUS My Cinema-U3100Mini Plus V2", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1957: { DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd393,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1958- &rtl28xxu_props, "GIGABYTE U7300", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1959: { DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1104,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1960- &rtl28xxu_props, "MSI DIGIVOX Micro HD", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1961: { DVB_USB_DEVICE(USB_VID_COMPRO, 0x0620,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1962- &rtl28xxu_props, "Compro VideoMate U620F", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1963: { DVB_USB_DEVICE(USB_VID_COMPRO, 0x0650,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1964- &rtl28xxu_props, "Compro VideoMate U650F", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1965: { DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd394,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1966- &rtl28xxu_props, "MaxMedia HU394-T", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1967: { DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6a03,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1968- &rtl28xxu_props, "Leadtek WinFast DTV Dongle mini", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1969: { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_CPYTO_REDI_PC50A,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1970- &rtl28xxu_props, "Crypto ReDi PC 50 A", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1971: { DVB_USB_DEVICE(USB_VID_KYE, 0x707f,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1972- &rtl28xxu_props, "Genius TVGo DVB-T03", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1973: { DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd395,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1974- &rtl28xxu_props, "Peak DVB-T USB", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1975: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV20_RTL2832U,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1976- &rtl28xxu_props, "Sveon STV20", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1977: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV21,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1978- &rtl28xxu_props, "Sveon STV21", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1979: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV27,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1980- &rtl28xxu_props, "Sveon STV27", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1981: { DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_TURBOX_DTT_2000,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1982- &rtl28xxu_props, "TURBO-X Pure TV Tuner DTT-2000", NULL) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1983: { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_PROLECTRIX_DV107669,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1984- &rtl28xxu_props, "PROlectrix DV107669", NULL) },
--
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1986- /* RTL2832P devices: */
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1987: { DVB_USB_DEVICE(USB_VID_HANFTEK, 0x0131,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1988- &rtl28xxu_props, "Astrometa DVB-T2",
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1989- RC_MAP_ASTROMETA_T2HYBRID) },
drivers/media/usb/dvb-usb-v2/rtl28xxu.c:1990: { DVB_USB_DEVICE(0x5654, 0xca42,
drivers/media/usb/dvb-usb-v2/rtl28xxu.c-1991- &rtl28xxu_props, "GoTView MasterHD 3", NULL) },
--
drivers/media/usb/dvb-usb-v2/zd1301.c=266=static const struct usb_device_id zd1301_id_table[] = {
drivers/media/usb/dvb-usb-v2/zd1301.c:267: {DVB_USB_DEVICE(USB_VID_ZYDAS, 0x13a1, &zd1301_props,
drivers/media/usb/dvb-usb-v2/zd1301.c-268- "ZyDAS ZD1301 reference design", NULL)},
]
|
| 122/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 102758
output: 54
thoughts: 1162
Thoughts: **Analyzing the I2C Adapter Block in the DVB-USB Driver**
Okay, let's break this down. First, I see that `0x8086` is the Intel Vendor ID (VID) and `0x9500` is the Intel CE9500 Product ID (PID). Quickly cross-referencing this with the `ce6230_id_table` in `drivers/media/usb/dvb-usb-v2/ce6230.c`, yep, `0x8086:0x9500` *is* the Intel CE9500 reference design. This means the reproducer is triggering `ce6230_probe`, which in turn calls `dvb_usbv2_probe`.
`dvb_usbv2_probe` then calls `dvb_usbv2_init`, which is where things get interesting. `dvb_usbv2_init` registers the I2C adapter. Then, crucially, `dvb_usbv2_init` seems to fail, perhaps due to missing endpoints or firmware, and this failure propagates back to `dvb_usbv2_probe`. Consequently, `dvb_usbv2_probe` calls `dvb_usbv2_exit`, which includes a call to `i2c_del_adapter`.
Now, here's the clincher: the reproducer is opening `/dev/i2c-#`, specifically `/dev/i2c-1` via `syz_open_dev`. This creates a race condition. The reproducer is *holding open* the file descriptor to the I2C adapter, probably `/dev/i2c-1`. This means the adapter is in use. Then, `i2c_del_adapter` gets called, and it blocks in `wait_for_completion(&adap->dev_released)`. This is where the hang happens; it's waiting for the reference to drop, but the reproducer's file descriptor is keeping that reference alive! This blocks the USB hub worker thread and causes a Denial of Service.
So, to summarize my analysis: the I2C adapter is registered, a process opens `/dev/i2c-1`, the probe fails, the driver tries to delete the adapter, the driver hangs waiting for the reference to drop, and the USB hub thread is blocked.
The question then becomes: can an unprivileged process trigger this? `/dev/i2c-#` is typically restricted to root or the `i2c` group, so that's a no. Can it be triggered in a user namespace? Nope, same restriction. Does sysfs or child devices hold a reference, preventing cleanup? No, and no. This bug is only exploitable when a reference to the I2C adapter exists that doesn't originate from `i2cdev_open` or other kernel-internal uses.
However, a new question arises: what if a user *unplugs* the USB device while `/dev/i2c-#` is open? The same block will occur! This is a known issue within the I2C subsystem (that `FIXME` comment hints at this).
So, while not a security vulnerability from an unprivileged perspective (requires a privileged user to have `/dev/i2c-#` open), it's still a significant denial-of-service risk, triggered by a peripheral device. A malicious USB device could cause `dvb_usbv2_probe` to fail and call `i2c_del_adapter`. But for the actual hang to occur, a privileged user *must* have `/dev/i2c-#` open. Without that, the I2C adapter will cleanly de-register. The USB device, by itself, can't trigger the hang. Does the driver itself, through some reference-leaking bug, hold a reference? Let's check `dvb_usbv2_init` for this scenario.
|
| 123/2 |
2026/05/30 16:47 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c Name:dvb_usbv2_init]
Results: map[SourceCode: 849: static int dvb_usbv2_init(struct dvb_usb_device *d)
850: {
851: int ret;
852: dev_dbg(&d->udev->dev, "%s:\n", __func__);
853:
854: dvb_usbv2_device_power_ctrl(d, 1);
855:
856: if (d->props->read_config) {
857: ret = d->props->read_config(d);
858: if (ret < 0)
859: goto err;
860: }
861:
862: ret = dvb_usbv2_i2c_init(d);
863: if (ret < 0)
864: goto err;
865:
866: ret = dvb_usbv2_adapter_init(d);
867: if (ret < 0)
868: goto err;
869:
870: if (d->props->init) {
871: ret = d->props->init(d);
872: if (ret < 0)
873: goto err;
874: }
875:
876: ret = dvb_usbv2_remote_init(d);
877: if (ret < 0)
878: goto err;
879:
880: dvb_usbv2_device_power_ctrl(d, 0);
881:
882: return 0;
883: err:
884: dvb_usbv2_device_power_ctrl(d, 0);
885: dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
886: return ret;
887: }
SourceFile:drivers/media/usb/dvb-usb-v2/dvb_usb_core.c]
|
| 124/2 |
2026/05/30 16:47 |
llm |
expert |
0m
Model:
Tokens:
input: 105420
output: 52
thoughts: 1993
Thoughts: **Analysis of the I2C Adapter Hang**
Okay, so the `dvb_usbv2_adapter_init` can fail, returning an error, and then `dvb_usbv2_probe` calls `dvb_usbv2_exit`, which calls `i2c_del_adapter`. No leak there, that's good. The hang, as reported by syzkaller, is explicitly caused by opening `/dev/i2c-#`. Since syzkaller runs as root, this isn't a problem for unprivileged users because they can't open `/dev/i2c-#`.
So, is this a Denial of Service? Technically, yes, if a privileged user does it. But privileged users can, in general, already cause DoS. Now, can a *malicious* USB device trigger a DoS without someone opening `/dev/i2c-#`? No, because `i2c_del_adapter` won't block without the extra reference.
But, are there other ways a reference could be held? What about `systemd-udevd`? It probes new devices, right? When `/dev/i2c-#` is created, it might open the device to read its properties, using `i2c-tools` or built-in rules. If it *does* open the device, and then the USB device disconnects or fails probing, `i2c_del_adapter` *would* block until `udevd` closes the file, but `udevd` closes very quickly. So that would be a transient block, not a permanent hang.
The syzkaller reproducer deliberately holds the file open, though. Specifically, the reproducer passes the file descriptor to `mount` with `9p`... I see: the reproducer is passing `r[1]` (which is the i2c file descriptor) and `r[0]` (the write file descriptor of a pipe) to `mount`.
Ah, looking at the code, the i2c fd is simply opened and leaked. The `mount` syscall is probably just a way to delay process termination, or just random syzkaller noise. Since the process doesn't exit immediately (because `mount` blocks, or the executor keeps the process alive), the i2c fd remains open, confirming the hang is caused by the leaked i2c fd.
Therefore, the bug requires: 1) Opening `/dev/i2c-#` and 2) the i2c adapter being removed (e.g. USB disconnect or probe failure). `/dev/i2c-#` is restricted, so this is inaccessible to unprivileged users. Is it a bug? Yes, it's a known architectural issue in the I2C subsystem. Is it a security vulnerability? For an unprivileged user, no. For a privileged user, they can already DoS. Can a malicious USB device trigger it? Only if it can trick a privileged process into holding `/dev/i2c-#` open. `udevd` only holds it open briefly. Therefore, a malicious USB device *cannot* cause a permanent hang.
Let's double-check, no way for a malicious device to provide a bad descriptor that leaks a reference, like with `dvb_usbv2_init`, which calls `dvb_usbv2_i2c_init`, which calls `i2c_add_adapter`. If `dvb_usbv2_adapter_init` fails, it returns, and then `dvb_usbv2_probe` calls `dvb_usbv2_exit`, which calls `dvb_usbv2_i2c_exit`, which calls `i2c_del_adapter`. There's no leak. The reference count is fine. The only reason it hangs is from the explicit `open` from userspace.
Reviewing the analysis:
* Exploitable: No. It's a hang (DoS) due to waiting for a reference to drop. No memory corruption.
* Denial of Service: Yes, it blocks the USB hub worker thread, preventing other USB devices from being processed.
* Accessible From Unprivileged Processes: No, requires opening `/dev/i2c-#` which is root/i2c only.
* Accessible From User Namespaces: No, user namespaces don't grant access to device nodes.
* VM Guest Trigger: No.
* VM Host Trigger in The Confidential Computing Context: No.
* Ethernet Network Trigger: No.
* Other Remote Trigger: No.
* Peripheral Trigger: Yes, a USB device is involved, but it requires a privileged user to open the device node to cause a permanent hang. So the peripheral alone cannot trigger the DoS. Wait, if the
|
| 125/2 |
2026/05/30 16:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/ce6230.c Name:ce6230_props]
Results: map[SourceCode: 232: /* DVB USB Driver stuff */
233: static struct dvb_usb_device_properties ce6230_props = {
234: .driver_name = KBUILD_MODNAME,
235: .owner = THIS_MODULE,
236: .adapter_nr = adapter_nr,
237: .bInterfaceNumber = 1,
238:
239: .i2c_algo = &ce6230_i2c_algorithm,
240: .power_ctrl = ce6230_power_ctrl,
241: .frontend_attach = ce6230_zl10353_frontend_attach,
242: .tuner_attach = ce6230_mxl5003s_tuner_attach,
243:
244: .num_adapters = 1,
245: .adapter = {
246: {
247: .stream = {
248: .type = USB_BULK,
249: .count = 6,
250: .endpoint = 0x82,
251: .u = {
252: .bulk = {
253: .buffersize = (16 * 512),
254: }
255: }
256: },
257: }
258: },
259: };
SourceFile:drivers/media/usb/dvb-usb-v2/ce6230.c]
|
| 126/2 |
2026/05/30 16:48 |
llm |
expert |
0m
Model:
Tokens:
input: 108940
output: 61
thoughts: 56
Thoughts: Okay, let's break this down.
**Diving into the Driver Initialization**
Right, so I'm looking at this driver and it looks like the key functions here are `frontend_attach` and `tuner_attach`. Interesting. I see that `dvb_usbv2_adapter_init` is calling `frontend_attach`. That's where it all starts. So, if I want to understand how the frontend is actually initialized, I need to trace my way through `frontend_attach`. Okay, let's jump right into `ce6230_zl10353_frontend_attach` and see what's happening there. That's the specific frontend driver implementation. Let's see what's happening.
|
| 127/2 |
2026/05/30 16:48 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/usb/dvb-usb-v2/ce6230.c Name:ce6230_zl10353_frontend_attach]
Results: map[SourceCode: 173: static int ce6230_zl10353_frontend_attach(struct dvb_usb_adapter *adap)
174: {
175: struct dvb_usb_device *d = adap_to_d(adap);
176:
177: dev_dbg(&d->udev->dev, "%s:\n", __func__);
178:
179: adap->fe[0] = dvb_attach(zl10353_attach, &ce6230_zl10353_config,
180: &d->i2c_adap);
181: if (adap->fe[0] == NULL)
182: return -ENODEV;
183:
184: return 0;
185: }
SourceFile:drivers/media/usb/dvb-usb-v2/ce6230.c]
|
| 128/2 |
2026/05/30 16:48 |
llm |
expert |
0m
Model:
Tokens:
input: 109487
output: 66
thoughts: 752
Thoughts: **My Analysis of the Potential Denial-of-Service**
Okay, let's break this down systematically. My understanding is that `dvb_attach` is just a macro that calls an attach function, so that doesn't tell us much. `zl10353_attach` allocates a state structure and returns a `struct dvb_frontend` pointer. Crucially, it doesn't register an I2C client device; it just uses the I2C adapter directly for communication. This means we're not dealing with any registered I2C client devices here. The only thing tying us to the I2C adapter is the open call through `i2cdev_open`.
Therefore, my initial conclusion is correct: this is a Denial of Service. It requires a privileged user to open and hold open a `/dev/i2c-#` device while the I2C adapter is removed. Given this requirement for a privileged user, it doesn't represent a *security* vulnerability in the typical sense. I'll mark all triggers as `False`, except for `DenialOfService` which is `True`, but with the caveat that it isn't a security issue since it's restricted to root access. Since the prompt asks about DoS specifically, I will set `DenialOfService` to `True`.
Let me think a bit more, and double check whether `i2cdev_open` could be reachable to an unprivileged user under *specific* configurations. Some distributions might indeed configure permissions on `/dev/i2c-#` to allow access to a specific group, like `i2c`. If an unprivileged user is part of the `i2c` group, they *could* trigger this DoS. However, we're operating under the assumption of a "typical (non-root) user process that does NOT have any special capabilities or access to device nodes restricted to root". Since `/dev/i2c-#` is usually restricted to root (or a special group), it falls under that "restricted to root" scenario. Therefore, `Unprivileged` must remain `False`.
Hmm, is there *any* other way this could be triggered? What if the reproducer leverages `mount` to hold the reference? Let's analyze the reproducer code. It starts with a `syz_open_dev` on `/dev/i2c-1` and gets a file descriptor, let's call it fd 3. Then, it creates a pipe with `pipe2` giving us fds 4 and 5. Then it opens a new file with `openat` and gets fd 6. Finally it does a `mount` with `trans=fd,rfdno=6,wfdno=5`. The key thing here is that the I2C fd (3) is NOT passed to `mount`. It's simply left open in the process's file descriptor table. This setup keeps the process alive because `mount` might block, or because the syzkaller executor holds onto it. Keeping the process alive, keeps fd 3 open, which keeps the reference to the I2C adapter. Then, if the USB device probe fails (or is disconnected by syzkaller), `i2c_del_adapter` blocks. This is exactly that `FIXME` that we discussed earlier, in `i2c_del_adapter`.
Yes, this is a correct analysis. I'm satisfied. Now, I'll go ahead and set the results and prepare the final reply.
|
| 129/2 |
2026/05/30 16:48 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 130/2 |
2026/05/30 16:48 |
llm |
expert |
0m
Model:
Tokens:
input: 111118
output: 719
thoughts: 380
Thoughts: **My Analysis of the I2C Subsystem Bug**
Okay, the results are in, and now it's time to craft the final response. I need to clearly articulate the nature of this bug and why, while it's a legitimate issue, it's not a security vulnerability exploitable by unprivileged users. Here's how I see it, step-by-step:
First, this isn't some obscure, undiscovered problem. It's rooted in a known architectural limitation within the I2C subsystem. I've seen it before; it's even flagged with a `FIXME` comment in `i2c_del_adapter()`. The code explicitly *blocks* using `wait_for_completion()`. This blocking behavior is *intentional*; it waits for all references to the I2C adapter to be released before de-allocating it.
The crux of the matter is how a user process can hold a reference to this adapter. It does so by opening the character device node, like `/dev/i2c-#`. This is critical.
The reproducer they're using involves a virtual USB device, specifically the Intel CE9500 DVB, which registers its own I2C adapter during its probe sequence: `dvb_usbv2_probe` calls `dvb_usbv2_init`, which then registers this adapter.
The reproducer then opens `/dev/i2c-1` and, crucially, *keeps the file descriptor open*. This is the hook.
Now, when the USB probe subsequently fails – or the device is disconnected – the driver calls `i2c_del_adapter()`. And *that's* when the problem hits. `i2c_del_adapter()` blocks indefinitely, waiting for the user process to close the `/dev/i2c-1` file descriptor. This causes the USB hub worker thread to hang.
The consequence? A Denial of Service (DoS). The USB hub worker is blocked, preventing it from handling other USB events. This is clearly undesirable.
However... and this is the vital point... this is where the security angle falls apart. By default, `/dev/i2c-#` device nodes are *restricted* to `root` (or a member of the privileged `i2c` group). Thus, any attempt by an unprivileged user to create this condition fails from the outset. They cannot open the `/dev/i2c-#` node.
Therefore, while a DoS *is* possible, it doesn't cross a security boundary. An unprivileged user cannot trigger this behavior. It's a bug, a DoS, but not a security vulnerability from their perspective. My response will clearly make that distinction.
|
| 131/1 |
2026/05/30 16:48 |
action |
format-explanation |
0m
Results: map[Explanation:The bug report describes a hung task in the USB hub worker thread
(`kworker/1:0`), specifically blocking in `i2c_del_adapter()`.
### Cause of the Bug
This is a known architectural limitation in the Linux I2C subsystem, which is
explicitly documented with a `FIXME` comment in `i2c_del_adapter()` (in
`drivers/i2c/i2c-core-base.c`):
```c
/* wait until all references to the device are gone
*
* FIXME: This is old code and should ideally be replaced by an
* alternative which results in decoupling the lifetime of the struct
* device from the i2c_adapter, like spi or netdev do. Any solution
* should be thoroughly tested with DEBUG_KOBJECT_RELEASE enabled!
*/
init_completion(&adap->dev_released);
device_unregister(&adap->dev);
wait_for_completion(&adap->dev_released);
```
When an I2C adapter is unregistered, `i2c_del_adapter()` intentionally blocks
and waits for all references to the `struct device` to be dropped.
In the provided reproducer:
1. A virtual USB device (Intel CE9500 DVB) is connected via the `raw-gadget`
interface.
2. The kernel starts probing the device (`dvb_usbv2_probe()`), which registers a
new I2C adapter.
3. Concurrently, the reproducer opens the newly created `/dev/i2c-#` character
device node and intentionally keeps the file descriptor open. Opening this
device node acquires a reference to the I2C adapter's `struct device`.
4. The USB device probe eventually fails (due to missing endpoints/firmware in
the simulated device), triggering the error path which calls
`i2c_del_adapter()`.
5. `i2c_del_adapter()` blocks indefinitely in `wait_for_completion()` because
the user-space process is still holding the file descriptor open, preventing the
device reference count from reaching zero.
6. Because this happens in the context of the USB hub worker thread
(`hub_event`), the worker is blocked, preventing the system from processing any
further USB plug/unplug events.
### Security Impact
While this bug can result in a Denial of Service (DoS) by hanging the USB
subsystem, **it is not a security vulnerability** for the following reasons:
* **Restricted Access:** To trigger this hang, a process must hold a reference
to the I2C adapter. The only way to do this from user-space is by opening the
`/dev/i2c-#` device node. By default, these device nodes are strictly owned by
`root` (or a privileged `i2c` group) with `0600` or `0660` permissions.
* **Unprivileged Users:** A typical unprivileged user cannot open `/dev/i2c-#`
and therefore cannot trigger this bug.
* **User Namespaces:** User namespaces do not grant access to restricted device
nodes in the initial namespace, so the bug cannot be exploited from within a
container/namespace either.
Since triggering this requires root or equivalent privileges (which already have
the ability to cause a DoS), this does not cross any security boundaries.]
|