| 0/0 |
2026/05/30 14:21 |
flow |
assessment-security |
58m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a stack out-of-bounds read and write (stack buffer overflow) in the
`ft260_smbus_xfer` function of the `hid-ft260` driver.
When handling SMBus block transfers, the core I2C subsystem passes a `union
i2c_smbus_data` structure to the adapter's transfer function. This union
contains a `block` array of 34 bytes, where `block[0]` specifies the length of
the payload (which must be $\le$ `I2C_SMBUS_BLOCK_MAX`, i.e., 32).
The `ft260_smbus_xfer` function fails to validate that `data->block[0]` is
within this limit. It only checks if the length is less than the FT260 USB
report size (60 bytes).
This missing validation leads to two distinct memory corruption scenarios:
1. **Via `/dev/i2c-X` ioctl (OOB Read & Write):**
When a user calls the `I2C_SMBUS` ioctl, `i2cdev_ioctl_smbus` allocates a
34-byte `union i2c_smbus_data temp` on the stack and copies the user-provided
data into it, including `temp.block[0]`.
- For an `I2C_SMBUS_BLOCK_DATA` write, `ft260_smbus_xfer` will read
`data->block[0] + 1` bytes from the stack and send them to the USB device. If
the user sets `block[0] = 50`, it reads 51 bytes from the 34-byte stack
buffer, leaking kernel stack memory to the USB device.
- For an `I2C_SMBUS_I2C_BLOCK_DATA` read, `ft260_smbus_xfer` will read
`data->block[0]` bytes from the USB device and write them into `temp.block +
1`. If the user sets `block[0] = 255`, it writes 255 bytes into the 34-byte
stack buffer, causing a massive stack buffer overflow that can overwrite the
return address and lead to arbitrary code execution.
2. **Via Kernel Drivers (Uninitialized Stack Memory):**
If a kernel driver (e.g., a hardware monitoring driver like `lm93`) calls
`i2c_smbus_read_block_data`, the core I2C code allocates an **uninitialized**
`union i2c_smbus_data` on the stack. `ft260_smbus_xfer` incorrectly uses the
uninitialized `data->block[0]` as the number of bytes to request from the USB
device. If the uninitialized stack memory happens to contain a large value,
the USB device's response will overflow the stack buffer.
**Security Impact:**
* **Exploitable:** **Yes.** The stack buffer overflow allows an attacker to
overwrite the stack frame (including the return address), which is a classic
vector for arbitrary code execution. The out-of-bounds read can be used to
bypass KASLR or leak stack canaries.
* **Denial Of Service:** **Yes.** Overwriting the stack will inevitably lead to
a kernel panic and system crash.
* **Accessible From Unprivileged Processes:** **No.** The `/dev/i2c-X` device
nodes are restricted to `root` (or the `i2c` group) by default. Triggering the
bug via hwmon sysfs files requires a specific hardware setup (a vulnerable hwmon
chip connected to the FT260) and relies on uninitialized stack memory, which is
not a reliable vector for a typical unprivileged user on a standard system.
* **Accessible From User Namespaces:** **No.** I2C device nodes are not
accessible within unprivileged user namespaces.
* **Peripheral Trigger:** **Yes.** An attacker with physical access can plug in
a malicious USB device that emulates an FT260 bridge. They can then either wait
for a privileged process to interact with the I2C bus, or emulate an I2C hwmon
device to trigger the uninitialized stack memory bug when the system
automatically probes and reads from the emulated sensors. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/30 14:21 |
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;
}
struct vusb_descriptor {
uint8_t req_type;
uint8_t desc_type;
uint32_t len;
char data[0];
} __attribute__((packed));
struct vusb_descriptors {
uint32_t len;
struct vusb_descriptor* generic;
struct vusb_descriptor* descs[0];
} __attribute__((packed));
struct vusb_response {
uint8_t type;
uint8_t req;
uint32_t len;
char data[0];
} __attribute__((packed));
struct vusb_responses {
uint32_t len;
struct vusb_response* generic;
struct vusb_response* resps[0];
} __attribute__((packed));
static bool lookup_control_response(const struct vusb_descriptors* descs, const struct vusb_responses* resps,
struct usb_ctrlrequest* ctrl, char** response_data, uint32_t* response_length)
{
int descs_num = 0;
int resps_num = 0;
if (descs)
descs_num = (descs->len - offsetof(struct vusb_descriptors, descs)) / sizeof(descs->descs[0]);
if (resps)
resps_num = (resps->len - offsetof(struct vusb_responses, resps)) / sizeof(resps->resps[0]);
uint8_t req = ctrl->bRequest;
uint8_t req_type = ctrl->bRequestType & USB_TYPE_MASK;
uint8_t desc_type = ctrl->wValue >> 8;
if (req == USB_REQ_GET_DESCRIPTOR) {
int i;
for (i = 0; i < descs_num; i++) {
struct vusb_descriptor* desc = descs->descs[i];
if (!desc)
continue;
if (desc->req_type == req_type && desc->desc_type == desc_type) {
*response_length = desc->len;
if (*response_length != 0)
*response_data = &desc->data[0];
else
*response_data = NULL;
return true;
}
}
if (descs && descs->generic) {
*response_data = &descs->generic->data[0];
*response_length = descs->generic->len;
return true;
}
} else {
int i;
for (i = 0; i < resps_num; i++) {
struct vusb_response* resp = resps->resps[i];
if (!resp)
continue;
if (resp->type == req_type && resp->req == req) {
*response_length = resp->len;
if (*response_length != 0)
*response_data = &resp->data[0];
else
*response_data = NULL;
return true;
}
}
if (resps && resps->generic) {
*response_data = &resps->generic->data[0];
*response_length = resps->generic->len;
return true;
}
}
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);
}
static int lookup_interface(int fd, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
for (int i = 0; i < index->ifaces_num; i++) {
if (index->ifaces[i].bInterfaceNumber == bInterfaceNumber &&
index->ifaces[i].bAlternateSetting == bAlternateSetting)
return i;
}
return -1;
}
#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 volatile long syz_usb_control_io(volatile long a0, volatile long a1, volatile long a2)
{
int fd = a0;
const struct vusb_descriptors* descs = (const struct vusb_descriptors*)a1;
const struct vusb_responses* resps = (const struct vusb_responses*)a2;
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = USB_MAX_PACKET_SIZE;
int rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL) {
return -1;
}
char* response_data = NULL;
uint32_t response_length = 0;
if ((event.ctrl.bRequestType & USB_DIR_IN) && event.ctrl.wLength) {
if (!lookup_control_response(descs, resps, &event.ctrl, &response_data, &response_length)) {
usb_raw_ep0_stall(fd);
return -1;
}
} else {
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD ||
event.ctrl.bRequest == USB_REQ_SET_INTERFACE) {
int iface_num = event.ctrl.wIndex;
int alt_set = event.ctrl.wValue;
int iface_index = lookup_interface(fd, iface_num, alt_set);
if (iface_index < 0) {
} else {
set_interface(fd, iface_index);
}
}
response_length = event.ctrl.wLength;
}
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;
if ((event.ctrl.bRequestType & USB_DIR_IN) && !event.ctrl.wLength) {
response_length = USB_MAX_PACKET_SIZE;
}
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) && event.ctrl.wLength) {
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 0;
}
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$hid arguments: [
// speed: usb_device_speed = 0x0 (8 bytes)
// dev_len: len = 0x36 (8 bytes)
// dev: ptr[in, usb_device_descriptor_hid] {
// usb_device_descriptor_hid {
// inner: usb_device_descriptor_verbose_t[flags[usb_versions, int16], 0, 0, 0, flags[usb_device_max_packet_sizes, int8], 0, 0, 64, array[usb_config_descriptor_hid, 1]] {
// bLength: const = 0x12 (1 bytes)
// bDescriptorType: const = 0x1 (1 bytes)
// bcdUSB: usb_versions = 0x0 (2 bytes)
// bDeviceClass: const = 0x0 (1 bytes)
// bDeviceSubClass: const = 0x0 (1 bytes)
// bDeviceProtocol: const = 0x0 (1 bytes)
// bMaxPacketSize0: usb_device_max_packet_sizes = 0x20 (1 bytes)
// idVendor: const = 0x403 (2 bytes)
// idProduct: const = 0x6030 (2 bytes)
// bcdDevice: const = 0x0 (2 bytes)
// iManufacturer: const = 0x0 (1 bytes)
// iProduct: const = 0x0 (1 bytes)
// iSerialNumber: const = 0x0 (1 bytes)
// bNumConfigurations: len = 0x1 (1 bytes)
// configs: array[usb_config_descriptor_hid] {
// usb_config_descriptor_hid {
// inner: usb_config_descriptor_verbose_t[const[1, int8], len[interfaces, int8], int8, flags[usb_config_attributes, int8], int8, void, array[usb_interface_descriptor_hid, 1]] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x2 (1 bytes)
// wTotalLength: len = 0x24 (2 bytes)
// bNumInterfaces: len = 0x1 (1 bytes)
// bConfigurationValue: const = 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_hid] {
// usb_interface_descriptor_hid {
// inner: usb_interface_descriptor_verbose_t[const[0, int8], int8, int8[1:2], const[USB_CLASS_HID, int8], const[USB_INTERFACE_SUBCLASS_BOOT, int8], flags[usb_hid_protocols, int8], int8, usb_hid_descriptor_hid, usb_endpoint_descriptors_hid] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x4 (1 bytes)
// bInterfaceNumber: const = 0x0 (1 bytes)
// bAlternateSetting: int8 = 0x0 (1 bytes)
// bNumEndpoints: int8 = 0x0 (1 bytes)
// bInterfaceClass: const = 0x3 (1 bytes)
// bInterfaceSubClass: const = 0x1 (1 bytes)
// bInterfaceProtocol: usb_hid_protocols = 0x0 (1 bytes)
// iInterface: int8 = 0x0 (1 bytes)
// extra: usb_hid_descriptor_hid {
// bLength: len = 0x0 (1 bytes)
// bDescriptorType: const = 0x21 (1 bytes)
// bcdHID: int16 = 0x0 (2 bytes)
// bCountryCode: int8 = 0x0 (1 bytes)
// bNumDescriptors: const = 0x1 (1 bytes)
// report_desc: usb_hid_class_descriptor_report {
// bDescriptorType: const = 0x22 (1 bytes)
// wDescriptorLength: int16 = 0x0 (2 bytes)
// }
// }
// endpoints: usb_endpoint_descriptors_hid {
// in: usb_endpoint_descriptor_hid_in {
// inner: usb_endpoint_descriptor_verbose_t[const[USB_ENDPOINT_HID_IN_ADDRESS, int8], const[USB_ENDPOINT_HID_ATTRIBUTES, int8], flags[usb_endpoint_max_packet_sizes, int16], int8, int8, int8, void] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x5 (1 bytes)
// bEndpointAddress: const = 0x81 (1 bytes)
// bmAttributes: const = 0x3 (1 bytes)
// wMaxPacketSize: usb_endpoint_max_packet_sizes = 0x0 (2 bytes)
// bInterval: int8 = 0x0 (1 bytes)
// bRefresh: int8 = 0x0 (1 bytes)
// bSynchAddress: int8 = 0x0 (1 bytes)
// extra: buffer: {} (length 0x0)
// }
// }
// out: array[usb_endpoint_descriptor_hid_out] {
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb_hid
*(uint8_t*)0x200000000040 = 0x12;
*(uint8_t*)0x200000000041 = 1;
*(uint16_t*)0x200000000042 = 0;
*(uint8_t*)0x200000000044 = 0;
*(uint8_t*)0x200000000045 = 0;
*(uint8_t*)0x200000000046 = 0;
*(uint8_t*)0x200000000047 = 0x20;
*(uint16_t*)0x200000000048 = 0x403;
*(uint16_t*)0x20000000004a = 0x6030;
*(uint16_t*)0x20000000004c = 0;
*(uint8_t*)0x20000000004e = 0;
*(uint8_t*)0x20000000004f = 0;
*(uint8_t*)0x200000000050 = 0;
*(uint8_t*)0x200000000051 = 1;
*(uint8_t*)0x200000000052 = 9;
*(uint8_t*)0x200000000053 = 2;
*(uint16_t*)0x200000000054 = 0x24;
*(uint8_t*)0x200000000056 = 1;
*(uint8_t*)0x200000000057 = 0;
*(uint8_t*)0x200000000058 = 0;
*(uint8_t*)0x200000000059 = 0;
*(uint8_t*)0x20000000005a = 0;
*(uint8_t*)0x20000000005b = 9;
*(uint8_t*)0x20000000005c = 4;
*(uint8_t*)0x20000000005d = 0;
*(uint8_t*)0x20000000005e = 0;
*(uint8_t*)0x20000000005f = 0;
*(uint8_t*)0x200000000060 = 3;
*(uint8_t*)0x200000000061 = 1;
*(uint8_t*)0x200000000062 = 0;
*(uint8_t*)0x200000000063 = 0;
*(uint8_t*)0x200000000064 = 0;
*(uint8_t*)0x200000000065 = 0x21;
*(uint16_t*)0x200000000066 = 0;
*(uint8_t*)0x200000000068 = 0;
*(uint8_t*)0x200000000069 = 1;
*(uint8_t*)0x20000000006a = 0x22;
*(uint16_t*)0x20000000006b = 0;
*(uint8_t*)0x20000000006d = 9;
*(uint8_t*)0x20000000006e = 5;
*(uint8_t*)0x20000000006f = 0x81;
*(uint8_t*)0x200000000070 = 3;
*(uint16_t*)0x200000000071 = 0;
*(uint8_t*)0x200000000073 = 0;
*(uint8_t*)0x200000000074 = 0;
*(uint8_t*)0x200000000075 = 0;
res = -1;
res = syz_usb_connect(/*speed=*/0, /*dev_len=*/0x36, /*dev=*/0x200000000040, /*conn_descs=*/0);
if (res != -1)
r[0] = res;
// syz_usb_control_io arguments: [
// fd: fd_usb (resource)
// descs: nil
// resps: nil
// ]
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0, /*resps=*/0);
// syz_usb_control_io$hid arguments: [
// fd: fd_usb_hid (resource)
// descs: ptr[in, vusb_descriptors_hid] {
// vusb_descriptors_hid {
// len: len = 0x24 (4 bytes)
// generic: nil
// USB_DT_STRING: nil
// HID_DT_REPORT: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {00 22 0b} (length 0x3)
// }
// }
// }
// HID_DT_HID: nil
// }
// }
// resps: nil
// ]
*(uint32_t*)0x200000000000 = 0x24;
*(uint64_t*)0x200000000004 = 0;
*(uint64_t*)0x20000000000c = 0;
*(uint64_t*)0x200000000014 = 0x2000000002c0;
memcpy((void*)0x2000000002c0, "\000\"\v", 3);
*(uint64_t*)0x20000000001c = 0;
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0x200000000000, /*resps=*/0);
// syz_usb_control_io arguments: [
// fd: fd_usb (resource)
// descs: nil
// resps: ptr[in, vusb_responses] {
// vusb_responses {
// len: len = 0x84 (4 bytes)
// generic: ptr[in, vusb_response_generic] {
// vusb_response_generic {
// type: usb_request_types = 0x0 (1 bytes)
// req: usb_requests = 0x14 (1 bytes)
// len: bytesize = 0xd (4 bytes)
// data: buffer: {5e 64 24 81 83 27 b2 36 9d ec a6 5e b2} (length 0xd)
// }
// }
// get_interface: nil
// get_configuration: nil
// hub_USB_REQ_GET_STATUS_hub: nil
// hub_USB_REQ_GET_STATUS_port: nil
// asix_AX_CMD_READ_MII_REG: nil
// asix_AX_CMD_STATMNGSTS_REG: nil
// asix_AX_CMD_READ_EEPROM: nil
// asix_AX_CMD_READ_RX_CTL: nil
// asix_AX_CMD_READ_NODE_ID: nil
// asix_AX88172_CMD_READ_NODE_ID: nil
// asix_AX_CMD_READ_PHY_ID: nil
// asix_AX_CMD_READ_MEDIUM_STATUS: nil
// asix_AX_CMD_READ_MONITOR_MODE: nil
// asix_AX_CMD_READ_GPIOS: nil
// asix_AX_CMD_SW_PHY_STATUS: nil
// }
// }
// ]
*(uint32_t*)0x200000000940 = 0x84;
*(uint64_t*)0x200000000944 = 0x2000000004c0;
*(uint8_t*)0x2000000004c0 = 0;
*(uint8_t*)0x2000000004c1 = 0x14;
*(uint32_t*)0x2000000004c2 = 0xd;
memcpy((void*)0x2000000004c6, "\x5e\x64\x24\x81\x83\x27\xb2\x36\x9d\xec\xa6\x5e\xb2", 13);
*(uint64_t*)0x20000000094c = 0;
*(uint64_t*)0x200000000954 = 0;
*(uint64_t*)0x20000000095c = 0;
*(uint64_t*)0x200000000964 = 0;
*(uint64_t*)0x20000000096c = 0;
*(uint64_t*)0x200000000974 = 0;
*(uint64_t*)0x20000000097c = 0;
*(uint64_t*)0x200000000984 = 0;
*(uint64_t*)0x20000000098c = 0;
*(uint64_t*)0x200000000994 = 0;
*(uint64_t*)0x20000000099c = 0;
*(uint64_t*)0x2000000009a4 = 0;
*(uint64_t*)0x2000000009ac = 0;
*(uint64_t*)0x2000000009b4 = 0;
*(uint64_t*)0x2000000009bc = 0;
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0, /*resps=*/0x200000000940);
// syz_usb_control_io arguments: [
// fd: fd_usb (resource)
// descs: nil
// resps: ptr[in, vusb_responses] {
// vusb_responses {
// len: len = 0x84 (4 bytes)
// generic: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {00 00 4d} (length 0x3)
// }
// }
// }
// get_interface: nil
// get_configuration: nil
// hub_USB_REQ_GET_STATUS_hub: nil
// hub_USB_REQ_GET_STATUS_port: nil
// asix_AX_CMD_READ_MII_REG: nil
// asix_AX_CMD_STATMNGSTS_REG: nil
// asix_AX_CMD_READ_EEPROM: nil
// asix_AX_CMD_READ_RX_CTL: nil
// asix_AX_CMD_READ_NODE_ID: nil
// asix_AX88172_CMD_READ_NODE_ID: nil
// asix_AX_CMD_READ_PHY_ID: nil
// asix_AX_CMD_READ_MEDIUM_STATUS: nil
// asix_AX_CMD_READ_MONITOR_MODE: nil
// asix_AX_CMD_READ_GPIOS: nil
// asix_AX_CMD_SW_PHY_STATUS: nil
// }
// }
// ]
*(uint32_t*)0x200000000880 = 0x84;
*(uint64_t*)0x200000000884 = 0x2000000003c0;
memcpy((void*)0x2000000003c0, "\000\000M", 3);
*(uint64_t*)0x20000000088c = 0;
*(uint64_t*)0x200000000894 = 0;
*(uint64_t*)0x20000000089c = 0;
*(uint64_t*)0x2000000008a4 = 0;
*(uint64_t*)0x2000000008ac = 0;
*(uint64_t*)0x2000000008b4 = 0;
*(uint64_t*)0x2000000008bc = 0;
*(uint64_t*)0x2000000008c4 = 0;
*(uint64_t*)0x2000000008cc = 0;
*(uint64_t*)0x2000000008d4 = 0;
*(uint64_t*)0x2000000008dc = 0;
*(uint64_t*)0x2000000008e4 = 0;
*(uint64_t*)0x2000000008ec = 0;
*(uint64_t*)0x2000000008f4 = 0;
*(uint64_t*)0x2000000008fc = 0;
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0, /*resps=*/0x200000000880);
// 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 = 0x2003 (8 bytes)
// ]
// returns fd_i2c
memcpy((void*)0x200000000040, "/dev/i2c-#\000", 11);
res = -1;
res = syz_open_dev(/*dev=*/0x200000000040, /*id=*/1, /*flags=FASYNC|O_RDWR|O_WRONLY*/0x2003);
if (res != -1)
r[1] = res;
// ioctl$I2C_SMBUS arguments: [
// fd: fd_i2c (resource)
// cmd: const = 0x720 (4 bytes)
// arg: ptr[in, i2c_smbus_ioctl_data] {
// i2c_smbus_ioctl_data {
// read_write: int8 = 0x0 (1 bytes)
// command: int8 = 0xab (1 bytes)
// pad = 0x0 (2 bytes)
// size: int32 = 0x8 (4 bytes)
// data: ptr[in, i2c_smbus_data] {
// i2c_smbus_data {
// size: int8 = 0x2a (1 bytes)
// block: buffer: {24 7f 23 e0 17 bb 48 6c 5c 4b 71 65 a6 d6 af 0b 92 0b f0 41 21 f0 33 94 00 00 00 00 00 00 00 00 00} (length 0x21)
// }
// }
// }
// }
// ]
*(uint8_t*)0x200000000200 = 0;
*(uint8_t*)0x200000000201 = 0xab;
*(uint32_t*)0x200000000204 = 8;
*(uint64_t*)0x200000000208 = 0x200000000180;
*(uint8_t*)0x200000000180 = 0x2a;
memcpy((void*)0x200000000181, "... [truncated large byte array] ...", 33);
syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0x720, /*arg=*/0x200000000200ul);
return 0;
}
]
|
| 2/1 |
2026/05/30 14:21 |
action |
kernel-checkouter |
2m
Results: map[KernelSrc:/app/workdir/cache/src/a4cabc769725e153406c1d66672b1e73cda3ae7a]
|
| 3/1 |
2026/05/30 14:24 |
action |
kernel-builder |
29m
Results: map[KernelObj:/app/workdir/cache/build/aa6327f0e338d7ac4cd3d67cd4d3b13403a1c485]
|
| 4/1 |
2026/05/30 14:53 |
action |
codesearch-prepare |
18m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/30 15:11 |
agent |
expert |
9m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true 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:
==================================================================
BUG: KASAN: stack-out-of-bounds in ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
Read of size 42 at addr ffffc9000ce27d21 by task syz.0.163/6206
CPU: 0 UID: 0 PID: 6206 Comm: syz.0.163 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xca/0x240 mm/kasan/report.c:482
kasan_report+0x118/0x150 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:-1 [inline]
kasan_check_range+0x2b0/0x2c0 mm/kasan/generic.c:200
__asan_memcpy+0x29/0x70 mm/kasan/shadow.c:105
ft260_smbus_write+0x156/0x2f0 drivers/hid/hid-ft260.c:486
ft260_smbus_xfer+0x8bc/0xdd0 drivers/hid/hid-ft260.c:-1
__i2c_smbus_xfer+0x557/0x1e20 drivers/i2c/i2c-core-smbus.c:591
i2c_smbus_xfer+0x1f4/0x310 drivers/i2c/i2c-core-smbus.c:546
i2cdev_ioctl_smbus+0x3db/0x750 drivers/i2c/i2c-dev.c:389
i2cdev_ioctl+0x5d3/0x820 drivers/i2c/i2c-dev.c:478
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xec/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f97ac58f749
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffcc9dab998 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f97ac7e5fa0 RCX: 00007f97ac58f749
RDX: 0000200000000200 RSI: 0000000000000720 RDI: 0000000000000004
RBP: 00007f97ac613f91 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f97ac7e5fa0 R14: 00007f97ac7e5fa0 R15: 0000000000000003
</TASK>
The buggy address belongs to stack of task syz.0.163/6206
and is located at offset 33 in frame:
i2cdev_ioctl_smbus+0x0/0x750 drivers/i2c/i2c-dev.c:317
This frame has 1 object:
[32, 66) 'temp'
The buggy address belongs to a 8-page vmalloc region starting at 0xffffc9000ce20000 allocated at copy_process+0x4e8/0x3ba0 kernel/fork.c:2049
The buggy address belongs to the physical page:
page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x27477
memcg:ffff888031c54a82
flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
raw: 00fff00000000000 0000000000000000 ffffea00009d1dc8 0000000000000000
raw: 0000000000000000 0000000000000000 00000001ffffffff ffff888031c54a82
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0x29c2(GFP_NOWAIT|__GFP_HIGHMEM|__GFP_IO|__GFP_FS|__GFP_ZERO), pid 5945, tgid 5945 (syz-executor), ts 175695068797, free_ts 175571170414
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x23d/0x2a0 mm/page_alloc.c:1887
prep_new_page mm/page_alloc.c:1895 [inline]
get_page_from_freelist+0x23a5/0x2440 mm/page_alloc.c:3948
__alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5236
alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2486
alloc_frozen_pages_noprof mm/mempolicy.c:2557 [inline]
alloc_pages_noprof+0xa9/0x190 mm/mempolicy.c:2577
vm_area_alloc_pages mm/vmalloc.c:3662 [inline]
__vmalloc_area_node mm/vmalloc.c:3876 [inline]
__vmalloc_node_range_noprof+0x795/0x16a0 mm/vmalloc.c:4064
__vmalloc_node_noprof+0xc2/0x110 mm/vmalloc.c:4124
alloc_thread_stack_node kernel/fork.c:354 [inline]
dup_task_struct+0x228/0x9a0 kernel/fork.c:923
copy_process+0x4e8/0x3ba0 kernel/fork.c:2049
kernel_clone+0x21e/0x820 kernel/fork.c:2648
__do_sys_clone kernel/fork.c:2789 [inline]
__se_sys_clone kernel/fork.c:2773 [inline]
__x64_sys_clone+0x18b/0x1e0 kernel/fork.c:2773
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xec/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 6195 tgid 6195 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
free_pages_prepare mm/page_alloc.c:1436 [inline]
__free_frozen_pages+0xb99/0xd10 mm/page_alloc.c:2976
discard_slab mm/slub.c:3580 [inline]
__put_partials+0x146/0x170 mm/slub.c:4120
__slab_free+0x294/0x320 mm/slub.c:6193
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x97/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:5187 [inline]
slab_alloc_node mm/slub.c:5500 [inline]
__do_kmalloc_node mm/slub.c:5893 [inline]
__kmalloc_noprof+0x3cf/0x800 mm/slub.c:5906
kmalloc_noprof include/linux/slab.h:972 [inline]
tomoyo_realpath_from_path+0xe3/0x5d0 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_path_number_perm+0x1e8/0x5a0 security/tomoyo/file.c:728
security_file_ioctl+0xcb/0x2d0 security/security.c:2435
__do_sys_ioctl fs/ioctl.c:591 [inline]
__se_sys_ioctl+0x47/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xec/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Memory state around the buggy address:
ffffc9000ce27c00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ffffc9000ce27c80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffffc9000ce27d00: f1 f1 f1 f1 00 00 00 00 02 f3 f3 f3 f3 f3 f3 f3
^
ffffc9000ce27d80: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
ffffc9000ce27e00: 04 f2 00 00 f2 f2 00 00 f3 f3 f3 f3 00 00 00 00
==================================================================
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;
}
struct vusb_descriptor {
uint8_t req_type;
uint8_t desc_type;
uint32_t len;
char data[0];
} __attribute__((packed));
struct vusb_descriptors {
uint32_t len;
struct vusb_descriptor* generic;
struct vusb_descriptor* descs[0];
} __attribute__((packed));
struct vusb_response {
uint8_t type;
uint8_t req;
uint32_t len;
char data[0];
} __attribute__((packed));
struct vusb_responses {
uint32_t len;
struct vusb_response* generic;
struct vusb_response* resps[0];
} __attribute__((packed));
static bool lookup_control_response(const struct vusb_descriptors* descs, const struct vusb_responses* resps,
struct usb_ctrlrequest* ctrl, char** response_data, uint32_t* response_length)
{
int descs_num = 0;
int resps_num = 0;
if (descs)
descs_num = (descs->len - offsetof(struct vusb_descriptors, descs)) / sizeof(descs->descs[0]);
if (resps)
resps_num = (resps->len - offsetof(struct vusb_responses, resps)) / sizeof(resps->resps[0]);
uint8_t req = ctrl->bRequest;
uint8_t req_type = ctrl->bRequestType & USB_TYPE_MASK;
uint8_t desc_type = ctrl->wValue >> 8;
if (req == USB_REQ_GET_DESCRIPTOR) {
int i;
for (i = 0; i < descs_num; i++) {
struct vusb_descriptor* desc = descs->descs[i];
if (!desc)
continue;
if (desc->req_type == req_type && desc->desc_type == desc_type) {
*response_length = desc->len;
if (*response_length != 0)
*response_data = &desc->data[0];
else
*response_data = NULL;
return true;
}
}
if (descs && descs->generic) {
*response_data = &descs->generic->data[0];
*response_length = descs->generic->len;
return true;
}
} else {
int i;
for (i = 0; i < resps_num; i++) {
struct vusb_response* resp = resps->resps[i];
if (!resp)
continue;
if (resp->type == req_type && resp->req == req) {
*response_length = resp->len;
if (*response_length != 0)
*response_data = &resp->data[0];
else
*response_data = NULL;
return true;
}
}
if (resps && resps->generic) {
*response_data = &resps->generic->data[0];
*response_length = resps->generic->len;
return true;
}
}
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);
}
static int lookup_interface(int fd, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
for (int i = 0; i < index->ifaces_num; i++) {
if (index->ifaces[i].bInterfaceNumber == bInterfaceNumber &&
index->ifaces[i].bAlternateSetting == bAlternateSetting)
return i;
}
return -1;
}
#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 volatile long syz_usb_control_io(volatile long a0, volatile long a1, volatile long a2)
{
int fd = a0;
const struct vusb_descriptors* descs = (const struct vusb_descriptors*)a1;
const struct vusb_responses* resps = (const struct vusb_responses*)a2;
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = USB_MAX_PACKET_SIZE;
int rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL) {
return -1;
}
char* response_data = NULL;
uint32_t response_length = 0;
if ((event.ctrl.bRequestType & USB_DIR_IN) && event.ctrl.wLength) {
if (!lookup_control_response(descs, resps, &event.ctrl, &response_data, &response_length)) {
usb_raw_ep0_stall(fd);
return -1;
}
} else {
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD ||
event.ctrl.bRequest == USB_REQ_SET_INTERFACE) {
int iface_num = event.ctrl.wIndex;
int alt_set = event.ctrl.wValue;
int iface_index = lookup_interface(fd, iface_num, alt_set);
if (iface_index < 0) {
} else {
set_interface(fd, iface_index);
}
}
response_length = event.ctrl.wLength;
}
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;
if ((event.ctrl.bRequestType & USB_DIR_IN) && !event.ctrl.wLength) {
response_length = USB_MAX_PACKET_SIZE;
}
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) && event.ctrl.wLength) {
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 0;
}
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$hid arguments: [
// speed: usb_device_speed = 0x0 (8 bytes)
// dev_len: len = 0x36 (8 bytes)
// dev: ptr[in, usb_device_descriptor_hid] {
// usb_device_descriptor_hid {
// inner: usb_device_descriptor_verbose_t[flags[usb_versions, int16], 0, 0, 0, flags[usb_device_max_packet_sizes, int8], 0, 0, 64, array[usb_config_descriptor_hid, 1]] {
// bLength: const = 0x12 (1 bytes)
// bDescriptorType: const = 0x1 (1 bytes)
// bcdUSB: usb_versions = 0x0 (2 bytes)
// bDeviceClass: const = 0x0 (1 bytes)
// bDeviceSubClass: const = 0x0 (1 bytes)
// bDeviceProtocol: const = 0x0 (1 bytes)
// bMaxPacketSize0: usb_device_max_packet_sizes = 0x20 (1 bytes)
// idVendor: const = 0x403 (2 bytes)
// idProduct: const = 0x6030 (2 bytes)
// bcdDevice: const = 0x0 (2 bytes)
// iManufacturer: const = 0x0 (1 bytes)
// iProduct: const = 0x0 (1 bytes)
// iSerialNumber: const = 0x0 (1 bytes)
// bNumConfigurations: len = 0x1 (1 bytes)
// configs: array[usb_config_descriptor_hid] {
// usb_config_descriptor_hid {
// inner: usb_config_descriptor_verbose_t[const[1, int8], len[interfaces, int8], int8, flags[usb_config_attributes, int8], int8, void, array[usb_interface_descriptor_hid, 1]] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x2 (1 bytes)
// wTotalLength: len = 0x24 (2 bytes)
// bNumInterfaces: len = 0x1 (1 bytes)
// bConfigurationValue: const = 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_hid] {
// usb_interface_descriptor_hid {
// inner: usb_interface_descriptor_verbose_t[const[0, int8], int8, int8[1:2], const[USB_CLASS_HID, int8], const[USB_INTERFACE_SUBCLASS_BOOT, int8], flags[usb_hid_protocols, int8], int8, usb_hid_descriptor_hid, usb_endpoint_descriptors_hid] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x4 (1 bytes)
// bInterfaceNumber: const = 0x0 (1 bytes)
// bAlternateSetting: int8 = 0x0 (1 bytes)
// bNumEndpoints: int8 = 0x0 (1 bytes)
// bInterfaceClass: const = 0x3 (1 bytes)
// bInterfaceSubClass: const = 0x1 (1 bytes)
// bInterfaceProtocol: usb_hid_protocols = 0x0 (1 bytes)
// iInterface: int8 = 0x0 (1 bytes)
// extra: usb_hid_descriptor_hid {
// bLength: len = 0x0 (1 bytes)
// bDescriptorType: const = 0x21 (1 bytes)
// bcdHID: int16 = 0x0 (2 bytes)
// bCountryCode: int8 = 0x0 (1 bytes)
// bNumDescriptors: const = 0x1 (1 bytes)
// report_desc: usb_hid_class_descriptor_report {
// bDescriptorType: const = 0x22 (1 bytes)
// wDescriptorLength: int16 = 0x0 (2 bytes)
// }
// }
// endpoints: usb_endpoint_descriptors_hid {
// in: usb_endpoint_descriptor_hid_in {
// inner: usb_endpoint_descriptor_verbose_t[const[USB_ENDPOINT_HID_IN_ADDRESS, int8], const[USB_ENDPOINT_HID_ATTRIBUTES, int8], flags[usb_endpoint_max_packet_sizes, int16], int8, int8, int8, void] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x5 (1 bytes)
// bEndpointAddress: const = 0x81 (1 bytes)
// bmAttributes: const = 0x3 (1 bytes)
// wMaxPacketSize: usb_endpoint_max_packet_sizes = 0x0 (2 bytes)
// bInterval: int8 = 0x0 (1 bytes)
// bRefresh: int8 = 0x0 (1 bytes)
// bSynchAddress: int8 = 0x0 (1 bytes)
// extra: buffer: {} (length 0x0)
// }
// }
// out: array[usb_endpoint_descriptor_hid_out] {
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb_hid
*(uint8_t*)0x200000000040 = 0x12;
*(uint8_t*)0x200000000041 = 1;
*(uint16_t*)0x200000000042 = 0;
*(uint8_t*)0x200000000044 = 0;
*(uint8_t*)0x200000000045 = 0;
*(uint8_t*)0x200000000046 = 0;
*(uint8_t*)0x200000000047 = 0x20;
*(uint16_t*)0x200000000048 = 0x403;
*(uint16_t*)0x20000000004a = 0x6030;
*(uint16_t*)0x20000000004c = 0;
*(uint8_t*)0x20000000004e = 0;
*(uint8_t*)0x20000000004f = 0;
*(uint8_t*)0x200000000050 = 0;
*(uint8_t*)0x200000000051 = 1;
*(uint8_t*)0x200000000052 = 9;
*(uint8_t*)0x200000000053 = 2;
*(uint16_t*)0x200000000054 = 0x24;
*(uint8_t*)0x200000000056 = 1;
*(uint8_t*)0x200000000057 = 0;
*(uint8_t*)0x200000000058 = 0;
*(uint8_t*)0x200000000059 = 0;
*(uint8_t*)0x20000000005a = 0;
*(uint8_t*)0x20000000005b = 9;
*(uint8_t*)0x20000000005c = 4;
*(uint8_t*)0x20000000005d = 0;
*(uint8_t*)0x20000000005e = 0;
*(uint8_t*)0x20000000005f = 0;
*(uint8_t*)0x200000000060 = 3;
*(uint8_t*)0x200000000061 = 1;
*(uint8_t*)0x200000000062 = 0;
*(uint8_t*)0x200000000063 = 0;
*(uint8_t*)0x200000000064 = 0;
*(uint8_t*)0x200000000065 = 0x21;
*(uint16_t*)0x200000000066 = 0;
*(uint8_t*)0x200000000068 = 0;
*(uint8_t*)0x200000000069 = 1;
*(uint8_t*)0x20000000006a = 0x22;
*(uint16_t*)0x20000000006b = 0;
*(uint8_t*)0x20000000006d = 9;
*(uint8_t*)0x20000000006e = 5;
*(uint8_t*)0x20000000006f = 0x81;
*(uint8_t*)0x200000000070 = 3;
*(uint16_t*)0x200000000071 = 0;
*(uint8_t*)0x200000000073 = 0;
*(uint8_t*)0x200000000074 = 0;
*(uint8_t*)0x200000000075 = 0;
res = -1;
res = syz_usb_connect(/*speed=*/0, /*dev_len=*/0x36, /*dev=*/0x200000000040, /*conn_descs=*/0);
if (res != -1)
r[0] = res;
// syz_usb_control_io arguments: [
// fd: fd_usb (resource)
// descs: nil
// resps: nil
// ]
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0, /*resps=*/0);
// syz_usb_control_io$hid arguments: [
// fd: fd_usb_hid (resource)
// descs: ptr[in, vusb_descriptors_hid] {
// vusb_descriptors_hid {
// len: len = 0x24 (4 bytes)
// generic: nil
// USB_DT_STRING: nil
// HID_DT_REPORT: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {00 22 0b} (length 0x3)
// }
// }
// }
// HID_DT_HID: nil
// }
// }
// resps: nil
// ]
*(uint32_t*)0x200000000000 = 0x24;
*(uint64_t*)0x200000000004 = 0;
*(uint64_t*)0x20000000000c = 0;
*(uint64_t*)0x200000000014 = 0x2000000002c0;
memcpy((void*)0x2000000002c0, "\000\"\v", 3);
*(uint64_t*)0x20000000001c = 0;
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0x200000000000, /*resps=*/0);
// syz_usb_control_io arguments: [
// fd: fd_usb (resource)
// descs: nil
// resps: ptr[in, vusb_responses] {
// vusb_responses {
// len: len = 0x84 (4 bytes)
// generic: ptr[in, vusb_response_generic] {
// vusb_response_generic {
// type: usb_request_types = 0x0 (1 bytes)
// req: usb_requests = 0x14 (1 bytes)
// len: bytesize = 0xd (4 bytes)
// data: buffer: {5e 64 24 81 83 27 b2 36 9d ec a6 5e b2} (length 0xd)
// }
// }
// get_interface: nil
// get_configuration: nil
// hub_USB_REQ_GET_STATUS_hub: nil
// hub_USB_REQ_GET_STATUS_port: nil
// asix_AX_CMD_READ_MII_REG: nil
// asix_AX_CMD_STATMNGSTS_REG: nil
// asix_AX_CMD_READ_EEPROM: nil
// asix_AX_CMD_READ_RX_CTL: nil
// asix_AX_CMD_READ_NODE_ID: nil
// asix_AX88172_CMD_READ_NODE_ID: nil
// asix_AX_CMD_READ_PHY_ID: nil
// asix_AX_CMD_READ_MEDIUM_STATUS: nil
// asix_AX_CMD_READ_MONITOR_MODE: nil
// asix_AX_CMD_READ_GPIOS: nil
// asix_AX_CMD_SW_PHY_STATUS: nil
// }
// }
// ]
*(uint32_t*)0x200000000940 = 0x84;
*(uint64_t*)0x200000000944 = 0x2000000004c0;
*(uint8_t*)0x2000000004c0 = 0;
*(uint8_t*)0x2000000004c1 = 0x14;
*(uint32_t*)0x2000000004c2 = 0xd;
memcpy((void*)0x2000000004c6, "\x5e\x64\x24\x81\x83\x27\xb2\x36\x9d\xec\xa6\x5e\xb2", 13);
*(uint64_t*)0x20000000094c = 0;
*(uint64_t*)0x200000000954 = 0;
*(uint64_t*)0x20000000095c = 0;
*(uint64_t*)0x200000000964 = 0;
*(uint64_t*)0x20000000096c = 0;
*(uint64_t*)0x200000000974 = 0;
*(uint64_t*)0x20000000097c = 0;
*(uint64_t*)0x200000000984 = 0;
*(uint64_t*)0x20000000098c = 0;
*(uint64_t*)0x200000000994 = 0;
*(uint64_t*)0x20000000099c = 0;
*(uint64_t*)0x2000000009a4 = 0;
*(uint64_t*)0x2000000009ac = 0;
*(uint64_t*)0x2000000009b4 = 0;
*(uint64_t*)0x2000000009bc = 0;
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0, /*resps=*/0x200000000940);
// syz_usb_control_io arguments: [
// fd: fd_usb (resource)
// descs: nil
// resps: ptr[in, vusb_responses] {
// vusb_responses {
// len: len = 0x84 (4 bytes)
// generic: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {00 00 4d} (length 0x3)
// }
// }
// }
// get_interface: nil
// get_configuration: nil
// hub_USB_REQ_GET_STATUS_hub: nil
// hub_USB_REQ_GET_STATUS_port: nil
// asix_AX_CMD_READ_MII_REG: nil
// asix_AX_CMD_STATMNGSTS_REG: nil
// asix_AX_CMD_READ_EEPROM: nil
// asix_AX_CMD_READ_RX_CTL: nil
// asix_AX_CMD_READ_NODE_ID: nil
// asix_AX88172_CMD_READ_NODE_ID: nil
// asix_AX_CMD_READ_PHY_ID: nil
// asix_AX_CMD_READ_MEDIUM_STATUS: nil
// asix_AX_CMD_READ_MONITOR_MODE: nil
// asix_AX_CMD_READ_GPIOS: nil
// asix_AX_CMD_SW_PHY_STATUS: nil
// }
// }
// ]
*(uint32_t*)0x200000000880 = 0x84;
*(uint64_t*)0x200000000884 = 0x2000000003c0;
memcpy((void*)0x2000000003c0, "\000\000M", 3);
*(uint64_t*)0x20000000088c = 0;
*(uint64_t*)0x200000000894 = 0;
*(uint64_t*)0x20000000089c = 0;
*(uint64_t*)0x2000000008a4 = 0;
*(uint64_t*)0x2000000008ac = 0;
*(uint64_t*)0x2000000008b4 = 0;
*(uint64_t*)0x2000000008bc = 0;
*(uint64_t*)0x2000000008c4 = 0;
*(uint64_t*)0x2000000008cc = 0;
*(uint64_t*)0x2000000008d4 = 0;
*(uint64_t*)0x2000000008dc = 0;
*(uint64_t*)0x2000000008e4 = 0;
*(uint64_t*)0x2000000008ec = 0;
*(uint64_t*)0x2000000008f4 = 0;
*(uint64_t*)0x2000000008fc = 0;
syz_usb_control_io(/*fd=*/r[0], /*descs=*/0, /*resps=*/0x200000000880);
// 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 = 0x2003 (8 bytes)
// ]
// returns fd_i2c
memcpy((void*)0x200000000040, "/dev/i2c-#\000", 11);
res = -1;
res = syz_open_dev(/*dev=*/0x200000000040, /*id=*/1, /*flags=FASYNC|O_RDWR|O_WRONLY*/0x2003);
if (res != -1)
r[1] = res;
// ioctl$I2C_SMBUS arguments: [
// fd: fd_i2c (resource)
// cmd: const = 0x720 (4 bytes)
// arg: ptr[in, i2c_smbus_ioctl_data] {
// i2c_smbus_ioctl_data {
// read_write: int8 = 0x0 (1 bytes)
// command: int8 = 0xab (1 bytes)
// pad = 0x0 (2 bytes)
// size: int32 = 0x8 (4 bytes)
// data: ptr[in, i2c_smbus_data] {
// i2c_smbus_data {
// size: int8 = 0x2a (1 bytes)
// block: buffer: {24 7f 23 e0 17 bb 48 6c 5c 4b 71 65 a6 d6 af 0b 92 0b f0 41 21 f0 33 94 00 00 00 00 00 00 00 00 00} (length 0x21)
// }
// }
// }
// }
// ]
*(uint8_t*)0x200000000200 = 0;
*(uint8_t*)0x200000000201 = 0xab;
*(uint32_t*)0x200000000204 = 8;
*(uint64_t*)0x200000000208 = 0x200000000180;
*(uint8_t*)0x200000000180 = 0x2a;
memcpy((void*)0x200000000181, "... [truncated large byte array] ...", 33);
syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0x720, /*arg=*/0x200000000200ul);
return 0;
}
Reply: **Bug Analysis:**
The bug is a stack out-of-bounds read and write (stack buffer overflow) in the `ft260_smbus_xfer` function of the `hid-ft260` driver.
When handling SMBus block transfers, the core I2C subsystem passes a `union i2c_smbus_data` structure to the adapter's transfer function. This union contains a `block` array of 34 bytes, where `block[0]` specifies the length of the payload (which must be $\le$ `I2C_SMBUS_BLOCK_MAX`, i.e., 32).
The `ft260_smbus_xfer` function fails to validate that `data->block[0]` is within this limit. It only checks if the length is less than the FT260 USB report size (60 bytes).
This missing validation leads to two distinct memory corruption scenarios:
1. **Via `/dev/i2c-X` ioctl (OOB Read & Write):**
When a user calls the `I2C_SMBUS` ioctl, `i2cdev_ioctl_smbus` allocates a 34-byte `union i2c_smbus_data temp` on the stack and copies the user-provided data into it, including `temp.block[0]`.
- For an `I2C_SMBUS_BLOCK_DATA` write, `ft260_smbus_xfer` will read `data->block[0] + 1` bytes from the stack and send them to the USB device. If the user sets `block[0] = 50`, it reads 51 bytes from the 34-byte stack buffer, leaking kernel stack memory to the USB device.
- For an `I2C_SMBUS_I2C_BLOCK_DATA` read, `ft260_smbus_xfer` will read `data->block[0]` bytes from the USB device and write them into `temp.block + 1`. If the user sets `block[0] = 255`, it writes 255 bytes into the 34-byte stack buffer, causing a massive stack buffer overflow that can overwrite the return address and lead to arbitrary code execution.
2. **Via Kernel Drivers (Uninitialized Stack Memory):**
If a kernel driver (e.g., a hardware monitoring driver like `lm93`) calls `i2c_smbus_read_block_data`, the core I2C code allocates an **uninitialized** `union i2c_smbus_data` on the stack. `ft260_smbus_xfer` incorrectly uses the uninitialized `data->block[0]` as the number of bytes to request from the USB device. If the uninitialized stack memory happens to contain a large value, the USB device's response will overflow the stack buffer.
**Security Impact:**
* **Exploitable:** **Yes.** The stack buffer overflow allows an attacker to overwrite the stack frame (including the return address), which is a classic vector for arbitrary code execution. The out-of-bounds read can be used to bypass KASLR or leak stack canaries.
* **Denial Of Service:** **Yes.** Overwriting the stack will inevitably lead to a kernel panic and system crash.
* **Accessible From Unprivileged Processes:** **No.** The `/dev/i2c-X` device nodes are restricted to `root` (or the `i2c` group) by default. Triggering the bug via hwmon sysfs files requires a specific hardware setup (a vulnerable hwmon chip connected to the FT260) and relies on uninitialized stack memory, which is not a reliable vector for a typical unprivileged user on a standard system.
* **Accessible From User Namespaces:** **No.** I2C device nodes are not accessible within unprivileged user namespaces.
* **Peripheral Trigger:** **Yes.** An attacker with physical access can plug in a malicious USB device that emulates an FT260 bridge. They can then either wait for a privileged process to interact with the I2C bus, or emulate an I2C hwmon device to trigger the uninitialized stack memory bug when the system automatically probes and reads from the emulated sensors.
|
| 6/2 |
2026/05/30 15:11 |
llm |
expert |
0m
Model:
Tokens:
input: 22669
output: 46
thoughts: 435
Thoughts: **KASAN Stack Out-of-Bounds Analysis**
Alright, let's break down this KASAN report. It appears we have a stack out-of-bounds read occurring within the `ft260_smbus_write` function. The report specifically flags a read of size 42 at address `ffffc9000ce27d21` by the `syz.0.163/6206` task. That's our starting point.
The call trace provides the flow: we have `ft260_smbus_write` calling `ft260_smbus_xfer`, which in turn leads to `__i2c_smbus_xfer` and then `i2c_smbus_xfer`. Ultimately, `i2cdev_ioctl_smbus` is involved and then `i2cdev_ioctl`. So we are deep in the I2C subsystem via the device node interface.
The report also isolates the buggy address to the stack of the `syz.0.163/6206` task, specifically at an offset of 33 within the `i2cdev_ioctl_smbus` frame. This frame contains a single object, `temp`, spanning from [32, 66). This is interesting, as that's pretty close to where the issue is.
My immediate thought is to examine the `i2cdev_ioctl_smbus` function in `drivers/i2c/i2c-dev.c`. Knowing the context around the `temp` variable will be crucial. We need to understand how it's used, how it's sized, and what data flows in and out of it, especially in the context of this `smbus` transfer. A careful inspection of the surrounding code should reveal the source of this out-of-bounds read and why we're seeing this.
|
| 7/2 |
2026/05/30 15:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_ioctl_smbus]
Results: map[SourceCode: 319: static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
320: u8 read_write, u8 command, u32 size,
321: union i2c_smbus_data __user *data)
322: {
323: union i2c_smbus_data temp = {};
324: int datasize, res;
325:
326: if ((size != I2C_SMBUS_BYTE) &&
327: (size != I2C_SMBUS_QUICK) &&
328: (size != I2C_SMBUS_BYTE_DATA) &&
329: (size != I2C_SMBUS_WORD_DATA) &&
330: (size != I2C_SMBUS_PROC_CALL) &&
331: (size != I2C_SMBUS_BLOCK_DATA) &&
332: (size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
333: (size != I2C_SMBUS_I2C_BLOCK_DATA) &&
334: (size != I2C_SMBUS_BLOCK_PROC_CALL)) {
335: dev_dbg(&client->adapter->dev,
336: "size out of range (%x) in ioctl I2C_SMBUS.\n",
337: size);
338: return -EINVAL;
339: }
340: /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
341: so the check is valid if size==I2C_SMBUS_QUICK too. */
342: if ((read_write != I2C_SMBUS_READ) &&
343: (read_write != I2C_SMBUS_WRITE)) {
344: dev_dbg(&client->adapter->dev,
345: "read_write out of range (%x) in ioctl I2C_SMBUS.\n",
346: read_write);
347: return -EINVAL;
348: }
349:
350: /* Note that command values are always valid! */
351:
352: if ((size == I2C_SMBUS_QUICK) ||
353: ((size == I2C_SMBUS_BYTE) &&
354: (read_write == I2C_SMBUS_WRITE)))
355: /* These are special: we do not use data */
356: return i2c_smbus_xfer(client->adapter, client->addr,
357: client->flags, read_write,
358: command, size, NULL);
359:
360: if (data == NULL) {
361: dev_dbg(&client->adapter->dev,
362: "data is NULL pointer in ioctl I2C_SMBUS.\n");
363: return -EINVAL;
364: }
365:
366: if ((size == I2C_SMBUS_BYTE_DATA) ||
367: (size == I2C_SMBUS_BYTE))
368: datasize = sizeof(data->byte);
369: else if ((size == I2C_SMBUS_WORD_DATA) ||
370: (size == I2C_SMBUS_PROC_CALL))
371: datasize = sizeof(data->word);
372: else /* size == smbus block, i2c block, or block proc. call */
373: datasize = sizeof(data->block);
374:
375: if ((size == I2C_SMBUS_PROC_CALL) ||
376: (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
377: (size == I2C_SMBUS_I2C_BLOCK_DATA) ||
378: (read_write == I2C_SMBUS_WRITE)) {
379: if (copy_from_user(&temp, data, datasize))
380: return -EFAULT;
381: }
382: if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
383: /* Convert old I2C block commands to the new
384: convention. This preserves binary compatibility. */
385: size = I2C_SMBUS_I2C_BLOCK_DATA;
386: if (read_write == I2C_SMBUS_READ)
387: temp.block[0] = I2C_SMBUS_BLOCK_MAX;
388: }
389: res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
390: read_write, command, size, &temp);
391: if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
392: (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
393: (read_write == I2C_SMBUS_READ))) {
394: if (copy_to_user(data, &temp, datasize))
395: return -EFAULT;
396: }
397: return res;
398: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 8/2 |
2026/05/30 15:11 |
llm |
expert |
0m
Model:
Tokens:
input: 25091
output: 45
thoughts: 39
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the I2C SMBus Data Structure**
Right, so we're looking at an initialization: `temp` is defined as a `union i2c_smbus_data`. Okay, let's just make sure we're on the same page and refresh my memory on what that structure actually *is*. Time to go look at the definition of `union i2c_smbus_data`. Need to understand the different data types contained inside. I need to understand what this union entails, it's possible uses, its layout and also to confirm the default initialization (or lack thereof) when it's declared this way.
|
| 9/2 |
2026/05/30 15:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2c_smbus_data]
Results: map[SourceCode: 142: union i2c_smbus_data {
143: __u8 byte;
144: __u16 word;
145: __u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
146: /* and one more for user-space compatibility */
147: };
SourceFile:include/uapi/linux/i2c.h]
|
| 10/2 |
2026/05/30 15:11 |
llm |
expert |
0m
Model:
Tokens:
input: 25467
output: 45
thoughts: 128
Thoughts: **Analyzing a Strange I2C Read**
Okay, so `I2C_SMBUS_BLOCK_MAX` is defined as 32, meaning my `block` buffer size should be 34 bytes (32 data bytes + 1 byte for length + 1 byte for PEC). That makes sense. The last byte would be at offset 33.
Wait a second... the report is showing a read of *42* bytes at that specific address (ffffc9000ce27d21) and the task `syz.0.163/6206`? That's not right. Where could it be reading from that would return 42 bytes when the bus is only configured for 32? It's even more perplexing that it looks like it's reading from `temp` at all.
Let me take a quick look at `drivers/hid/hid-ft260.c`, specifically the `ft260_smbus_write` function. Something isn't adding up.
|
| 11/2 |
2026/05/30 15:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_smbus_write]
Results: map[SourceCode: 465: static int ft260_smbus_write(struct ft260_device *dev, u8 addr, u8 cmd,
466: u8 *data, u8 data_len, u8 flag)
467: {
468: int ret = 0;
469: int len = 4;
470:
471: struct ft260_i2c_write_request_report *rep =
472: (struct ft260_i2c_write_request_report *)dev->write_buf;
473:
474: if (data_len >= sizeof(rep->data))
475: return -EINVAL;
476:
477: rep->address = addr;
478: rep->data[0] = cmd;
479: rep->length = data_len + 1;
480: rep->flag = flag;
481: len += rep->length;
482:
483: rep->report = FT260_I2C_DATA_REPORT_ID(len);
484:
485: if (data_len > 0)
486: memcpy(&rep->data[1], data, data_len);
487:
488: ft260_dbg("rep %#02x addr %#02x cmd %#02x datlen %d replen %d\n",
489: rep->report, addr, cmd, rep->length, len);
490:
491: ret = ft260_hid_output_report_check_status(dev, (u8 *)rep, len);
492:
493: return ret;
494: }
SourceFile:drivers/hid/hid-ft260.c]
|
| 12/2 |
2026/05/30 15:11 |
llm |
expert |
0m
Model:
Tokens:
input: 26390
output: 46
thoughts: 38
Thoughts: Here's a breakdown of my thought process:
**Diving into the Data Flow**
Okay, the `data` pointer, that's the crucial element in this `ft260_smbus_xfer` function. My immediate focus is on *how* that pointer is being passed and used. Where did it originate? The code indicates it's passed from `ft260_smbus_xfer`. So the next step is to examine the function `ft260_smbus_xfer` itself. I need to understand its behavior and how it interacts with the `data` pointer. It's a fundamental part of understanding the transfer.
|
| 13/2 |
2026/05/30 15:11 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_smbus_xfer]
Results: map[SourceCode: 652: static int ft260_smbus_xfer(struct i2c_adapter *adapter, u16 addr, u16 flags,
653: char read_write, u8 cmd, int size,
654: union i2c_smbus_data *data)
655: {
656: int ret;
657: struct ft260_device *dev = i2c_get_adapdata(adapter);
658: struct hid_device *hdev = dev->hdev;
659:
660: ft260_dbg("smbus size %d\n", size);
661:
662: mutex_lock(&dev->lock);
663:
664: ret = hid_hw_power(hdev, PM_HINT_FULLON);
665: if (ret < 0) {
666: hid_err(hdev, "power management error: %d\n", ret);
667: mutex_unlock(&dev->lock);
668: return ret;
669: }
670:
671: switch (size) {
672: case I2C_SMBUS_BYTE:
673: if (read_write == I2C_SMBUS_READ)
674: ret = ft260_i2c_read(dev, addr, &data->byte, 1,
675: FT260_FLAG_START_STOP);
676: else
677: ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
678: FT260_FLAG_START_STOP);
679: break;
680: case I2C_SMBUS_BYTE_DATA:
681: if (read_write == I2C_SMBUS_READ) {
682: ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
683: FT260_FLAG_START);
684: if (ret)
685: goto smbus_exit;
686:
687: ret = ft260_i2c_read(dev, addr, &data->byte, 1,
688: FT260_FLAG_START_STOP_REPEATED);
689: } else {
690: ret = ft260_smbus_write(dev, addr, cmd, &data->byte, 1,
691: FT260_FLAG_START_STOP);
692: }
693: break;
694: case I2C_SMBUS_WORD_DATA:
695: if (read_write == I2C_SMBUS_READ) {
696: ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
697: FT260_FLAG_START);
698: if (ret)
699: goto smbus_exit;
700:
701: ret = ft260_i2c_read(dev, addr, (u8 *)&data->word, 2,
702: FT260_FLAG_START_STOP_REPEATED);
703: } else {
704: ret = ft260_smbus_write(dev, addr, cmd,
705: (u8 *)&data->word, 2,
706: FT260_FLAG_START_STOP);
707: }
708: break;
709: case I2C_SMBUS_BLOCK_DATA:
710: if (read_write == I2C_SMBUS_READ) {
711: ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
712: FT260_FLAG_START);
713: if (ret)
714: goto smbus_exit;
715:
716: ret = ft260_i2c_read(dev, addr, data->block,
717: data->block[0] + 1,
718: FT260_FLAG_START_STOP_REPEATED);
719: } else {
720: ret = ft260_smbus_write(dev, addr, cmd, data->block,
721: data->block[0] + 1,
722: FT260_FLAG_START_STOP);
723: }
724: break;
725: case I2C_SMBUS_I2C_BLOCK_DATA:
726: if (read_write == I2C_SMBUS_READ) {
727: ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
728: FT260_FLAG_START);
729: if (ret)
730: goto smbus_exit;
731:
732: ret = ft260_i2c_read(dev, addr, data->block + 1,
733: data->block[0],
734: FT260_FLAG_START_STOP_REPEATED);
735: } else {
736: ret = ft260_smbus_write(dev, addr, cmd, data->block + 1,
737: data->block[0],
738: FT260_FLAG_START_STOP);
739: }
740: break;
741: default:
742: hid_err(hdev, "unsupported smbus transaction size %d\n", size);
743: ret = -EOPNOTSUPP;
744: }
745:
746: smbus_exit:
747: hid_hw_power(hdev, PM_HINT_NORMAL);
748: mutex_unlock(&dev->lock);
749: return ret;
750: }
SourceFile:drivers/hid/hid-ft260.c]
|
| 14/2 |
2026/05/30 15:11 |
llm |
expert |
0m
Model:
Tokens:
input: 28344
output: 50
thoughts: 433
Thoughts: **Vulnerability Analysis: I2C SMBus Block Data Write**
Okay, let's break this down. In the `I2C_SMBUS_BLOCK_DATA` write operation, I see a potential issue. The user-space controls the value of `data->block[0]`. If it's a read, it doesn't matter as much, but on the write side, `ft260_smbus_write` is called, and a crucial parameter is derived from that user-provided `data->block[0]`. Specifically, `data_len` becomes `data->block[0] + 1`.
Looking further back at how `data->block` gets populated, it seems that in `i2cdev_ioctl_smbus`, the code copies directly from user space into a local variable called `temp`, which is of type `data->block`. Crucially, `datasize` is `sizeof(data->block)`, which is hardcoded to 34 bytes. Then that `temp` variable is copied to `data->block` and passed to `ft260_smbus_write` via the intermediate `i2c_smbus_xfer` function.
The problem here is that if a user-space application sets `data->block[0]` to, say, 255, the `data_len` passed to `ft260_smbus_write` would be 256. This means the code attempts to read 256 bytes from an array which contains 34 bytes (due to the sizeof(data->block) size constraint). However, `ft260_smbus_write` has a check: `if (data_len >= sizeof(rep->data)) return -EINVAL;`. So, I'm now curious about `sizeof(rep->data)`. This likely presents a buffer overflow if `sizeof(rep->data)` is less than 256. It warrants further investigation.
|
| 15/2 |
2026/05/30 15:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_i2c_write_request_report]
Results: map[SourceCode: 208: struct ft260_i2c_write_request_report {
209: u8 report; /* FT260_I2C_REPORT */
210: u8 address; /* 7-bit I2C address */
211: u8 flag; /* I2C transaction condition */
212: u8 length; /* data payload length */
213: u8 data[FT260_WR_DATA_MAX]; /* data payload */
214: } __packed;
SourceFile:drivers/hid/hid-ft260.c]
|
| 16/2 |
2026/05/30 15:12 |
llm |
expert |
0m
Model:
Tokens:
input: 29431
output: 46
thoughts: 16
Thoughts: Okay, let's break this down. Here's my understanding, presented as if I'm working through it:
**Considering `FT260_WR_DATA_MAX`**
Alright, so I'm looking at `FT260_WR_DATA_MAX`. This instantly tells me we're dealing with the FT260 chip and, more specifically, a maximum data write size or buffer limit. Immediately, I need to consider the implications. Is this a hardware limitation of the FT260 itself? Or a limitation imposed by the firmware/driver we're using? Knowing this will influence my approach to optimizing data transfer. I wonder if this value is fixed, or if it's configurable in any way... potentially through a register setting, a define in the header files, or even an API function. Is the current value appropriate for the application? Does it impact performance? I need to think about the data types being written; are we dealing with byte streams, strings, or something else? Understanding the context of how this maximum write size is being used will be critical to fully grasping the potential bottlenecks and opportunities for optimization. I'll need to dig into the FT260 documentation, the driver source code, and perhaps even the application's implementation to see how it's being handled.
|
| 17/2 |
2026/05/30 15:12 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:FT260_WR_DATA_MAX]
Results: map[SourceCode: SourceFile:]
|
| 18/2 |
2026/05/30 15:12 |
llm |
expert |
0m
Model:
Tokens:
input: 29790
output: 24
thoughts: 17
Thoughts: Here's my thought process, summarized as you requested:
**Digging into the Data Limits**
Okay, so I need to find the maximum write data size related to the FT260. My first instinct, as always, is to go straight to the source and *grep* for it. The target is clearly something named `FT260_WR_DATA_MAX`. This approach is quick, efficient, and lets me zero in on the relevant code or documentation directly. It's the most likely way to rapidly surface the constant's definition. Let's see what turns up.
|
| 19/2 |
2026/05/30 15:12 |
tool |
grepper |
0m
Args: map[Expression:FT260_WR_DATA_MAX]
Results: map[Output:drivers/hid/hid-ft260.c=23=MODULE_PARM_DESC(debug, "Toggle FT260 debugging messages");
--
drivers/hid/hid-ft260.c-47-#define FT260_RD_DATA_MAX (180)
drivers/hid/hid-ft260.c:48:#define FT260_WR_DATA_MAX (60)
drivers/hid/hid-ft260.c-49-
--
drivers/hid/hid-ft260.c=208=struct ft260_i2c_write_request_report {
--
drivers/hid/hid-ft260.c-212- u8 length; /* data payload length */
drivers/hid/hid-ft260.c:213: u8 data[FT260_WR_DATA_MAX]; /* data payload */
drivers/hid/hid-ft260.c-214-} __packed;
--
drivers/hid/hid-ft260.c=417=static int ft260_i2c_write(struct ft260_device *dev, u8 addr, u8 *data,
--
drivers/hid/hid-ft260.c-430- do {
drivers/hid/hid-ft260.c:431: if (len <= FT260_WR_DATA_MAX) {
drivers/hid/hid-ft260.c-432- wr_len = len;
--
drivers/hid/hid-ft260.c-435- } else {
drivers/hid/hid-ft260.c:436: wr_len = FT260_WR_DATA_MAX;
drivers/hid/hid-ft260.c-437- }
]
|
| 20/2 |
2026/05/30 15:12 |
llm |
expert |
0m
Model:
Tokens:
input: 30427
output: 49
thoughts: 1004
Thoughts: **Analyzing a Potential Stack Out-of-Bounds Read in the FT260 I2C Driver**
Okay, so I'm digging into this FT260 I2C driver, and there's something that's making me raise an eyebrow. It looks like `FT260_WR_DATA_MAX` is defined as 60. Now, when I look at `ft260_smbus_write`, it checks if the data length (`data_len`) is greater than or equal to `sizeof(rep->data)`, which is also 60. So, `data_len` can be, at most, 59. That makes sense, so far.
But here's where it gets interesting. `data->block` in `ft260_smbus_write` is a mere 34 bytes! Let's say `data->block[0]` is, say, 41, then `data_len` will be 42. And, critically, this passes the initial check because `data_len` (42) is less than 60. Then, `memcpy(&rep->data[1], data, data_len);` copies `data_len` bytes from the stack buffer.
Hold on, `data` in `ft260_smbus_write` *is* actually `data->block`, which comes from `ft260_smbus_xfer`, and specifically from the stack-allocated `union i2c_smbus_data temp` inside `i2cdev_ioctl_smbus`. So, we're copying 42 bytes from a 34-byte buffer (on the stack)! That's an 8-byte stack out-of-bounds read!
The key question is, can this be exploited? The code reads 8 bytes from the stack past `temp` and sends this data to the I2C device, which is an FT260 USB HID device. If an attacker can control the USB device or read the state of the I2C device, they could see these 8 bytes.
The immediate thought is, if they *can* plug in a malicious USB device, the game is already over; they can do so much more than this. But can this vulnerability be triggered without a custom USB device?
The code is triggered when the I2C SMBUS ioctl is called on a `/dev/i2c-X` device, and if the device is backed by the `hid-ft260` driver. So, we need an FT260 device connected to the system. Assuming that's the case, an unprivileged user *could* potentially trigger this. If they have access to `/dev/i2c-X`, they could send a crafted command that leads to the out-of-bounds read. That's the scenario I am trying to determine.
However, `/dev/i2c-X` is *typically* owned by root:i2c, with permissions 0660. So, by default, unprivileged users can't open it unless they are in the `i2c` group. It's usually root:root or root:i2c. Ah, here's the catch! The prompt specifically wants to know if it's exploitable by a non-root user with no special capabilities. Since access to `/dev/i2c-X` is restricted to root or the `i2c` group, a *typical* unprivileged user can't reach this code path.
Okay, so maybe there's another way? Is there *any* other way to call `ft260_smbus_xfer`? The driver is registered as an I2C adapter. So, theoretically, any kernel code that does I2C transfers on *this* adapter could trigger it. But the crucial out-of-bounds read is specific to the `i2cdev_ioctl_smbus` path. It is because that ioctl function is the only one allocating a limited stack buffer while `ft260_smbus_xfer` allows the copy of a potentially longer block based on what it reads from the buffer.
Now, an important point. `i2c_smbus_xfer` is the generic I2C API. Does it *guarantee* that `data->block[0]` is at most `I2C_SMBUS_BLOCK_MAX` (32 bytes)? I need to check the definition of `i2c_smbus_xfer` to confirm this.
|
| 21/2 |
2026/05/30 15:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-smbus.c Name:i2c_smbus_xfer]
Results: map[SourceCode: 523: /**
524: * i2c_smbus_xfer - execute SMBus protocol operations
525: * @adapter: Handle to I2C bus
526: * @addr: Address of SMBus slave on that bus
527: * @flags: I2C_CLIENT_* flags (usually zero or I2C_CLIENT_PEC)
528: * @read_write: I2C_SMBUS_READ or I2C_SMBUS_WRITE
529: * @command: Byte interpreted by slave, for protocols which use such bytes
530: * @protocol: SMBus protocol operation to execute, such as I2C_SMBUS_PROC_CALL
531: * @data: Data to be read or written
532: *
533: * This executes an SMBus protocol operation, and returns a negative
534: * errno code else zero on success.
535: */
536: s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
537: unsigned short flags, char read_write,
538: u8 command, int protocol, union i2c_smbus_data *data)
539: {
540: s32 res;
541:
542: res = __i2c_lock_bus_helper(adapter);
543: if (res)
544: return res;
545:
546: res = __i2c_smbus_xfer(adapter, addr, flags, read_write,
547: command, protocol, data);
548: i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);
549:
550: return res;
551: }
SourceFile:drivers/i2c/i2c-core-smbus.c]
|
| 22/2 |
2026/05/30 15:12 |
llm |
expert |
0m
Model:
Tokens:
input: 33014
output: 50
thoughts: 16
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Diving into `__i2c_smbus_xfer`**
Alright, let's start by digging into `__i2c_smbus_xfer`. I know this is the low-level workhorse for SMBus transactions within the kernel's I2C framework. I need to refresh my memory on the intricacies of its implementation. I'm thinking about the data structures it uses, particularly the `i2c_msg` structure and how it relates to the underlying I2C adapter driver.
Specifically, I need to consider potential areas for optimization or where a subtle bug might be hiding. Is there any obvious place where we can bypass unnecessary data copies? Or could the locking mechanism be improved without introducing new race conditions? I need to analyze how it handles the different SMBus transaction types and how it interacts with the adapter's capabilities. Let's see if this function is as efficient as it can be. Perhaps I should go over its error handling procedures, too.
|
| 23/2 |
2026/05/30 15:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-smbus.c Name:__i2c_smbus_xfer]
Results: map[SourceCode: 554: s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
555: unsigned short flags, char read_write,
556: u8 command, int protocol, union i2c_smbus_data *data)
557: {
558: int (*xfer_func)(struct i2c_adapter *adap, u16 addr,
559: unsigned short flags, char read_write,
560: u8 command, int size, union i2c_smbus_data *data);
561: unsigned long orig_jiffies;
562: int try;
563: s32 res;
564:
565: res = __i2c_check_suspended(adapter);
566: if (res)
567: return res;
568:
569: /* If enabled, the following two tracepoints are conditional on
570: * read_write and protocol.
571: */
572: trace_smbus_write(adapter, addr, flags, read_write,
573: command, protocol, data);
574: trace_smbus_read(adapter, addr, flags, read_write,
575: command, protocol);
576:
577: flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB;
578:
579: xfer_func = adapter->algo->smbus_xfer;
580: if (i2c_in_atomic_xfer_mode()) {
581: if (adapter->algo->smbus_xfer_atomic)
582: xfer_func = adapter->algo->smbus_xfer_atomic;
583: else if (adapter->algo->master_xfer_atomic)
584: xfer_func = NULL; /* fallback to I2C emulation */
585: }
586:
587: if (xfer_func) {
588: /* Retry automatically on arbitration loss */
589: orig_jiffies = jiffies;
590: for (res = 0, try = 0; try <= adapter->retries; try++) {
591: res = xfer_func(adapter, addr, flags, read_write,
592: command, protocol, data);
593: if (res != -EAGAIN)
594: break;
595: if (time_after(jiffies,
596: orig_jiffies + adapter->timeout))
597: break;
598: }
599:
600: if (res != -EOPNOTSUPP || !adapter->algo->master_xfer)
601: goto trace;
602: /*
603: * Fall back to i2c_smbus_xfer_emulated if the adapter doesn't
604: * implement native support for the SMBus operation.
605: */
606: }
607:
608: res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
609: command, protocol, data);
610:
611: trace:
612: /* If enabled, the reply tracepoint is conditional on read_write. */
613: trace_smbus_reply(adapter, addr, flags, read_write,
614: command, protocol, data, res);
615: trace_smbus_result(adapter, addr, flags, read_write,
616: command, protocol, res);
617:
618: return res;
619: }
SourceFile:drivers/i2c/i2c-core-smbus.c]
|
| 24/2 |
2026/05/30 15:12 |
llm |
expert |
0m
Model:
Tokens:
input: 34403
output: 46
thoughts: 1647
Thoughts: **Analysis of the FT260 I2C Driver Vulnerability**
Okay, so here's what I've got. I'm looking at this `i2cdev_ioctl_smbus` function and immediately a potential issue jumps out. It copies `data->block` from userspace, which is concerning. Specifically, for SMBUS block calls, if it's a write and the `size` is something like `I2C_SMBUS_BLOCK_DATA`, it copies `datasize` bytes, and `datasize` comes from `sizeof(data->block)`, which is 34 bytes. That means the crucial `temp.block[0]` is completely controlled by the user.
Then, `i2c_smbus_xfer` calls `ft260_smbus_xfer`. Inside `ft260_smbus_xfer`, when it hits the `I2C_SMBUS_BLOCK_DATA` case for a write, it uses the user-provided value `data->block[0]` to determine the length of the write. If `data->block[0]` is say, 50, it calculates `data->block[0] + 1` which is 51. It then does a check of the data length. If the value is > 59, which it is in our scenario, the driver checks this and then does a `memcpy` using that calculated length. `data->block` is a 34-byte array from `temp`, and the code reads `data->block[0] + 1` bytes starting at `data->block`. So if `data->block[0]` is 50, it reads 51 bytes. That's a 17-byte out-of-bounds read from the stack!
Wait, the bug report specifies a read of 42 bytes. That means `data->block[0]` was 41, which matches the behavior perfectly.
Can this be exploited? It's a stack out-of-bounds read, and the data is being sent to a USB device, the FT260. If the attacker controls the USB device, the data can be used to leak stack memory. However, the reproducer uses `syz_usb_connect` using `CONFIG_USB_RAW_GADGET` or `CONFIG_USB_DUMMY_HCD` to emulate a USB device. This allows an unprivileged user to do it, although `/dev/raw-gadget` is normally restricted to root. If they can open `/dev/i2c-X` and issue the ioctl, the out-of-bounds stack data is sent to the legitimate FT260 device. Can they read it back? Not unless the FT260 echoes it, or they have another means of observing the bus. It's an information leak if they can send out-of-bounds data to the I2C bus. But `/dev/i2c-X` is typically only accessible to root or the `i2c` group. Therefore, it's not accessible from an unprivileged process. This access restriction blocks it.
I need to re-evaluate how `ft260_smbus_xfer` is reached. The FT260 registers an I2C adapter. Are there any other drivers that might use this I2C adapter to expose an unprivileged interface to reach the vulnerable code?
Okay, the root of the problem is `ft260_smbus_xfer` trusting `data->block[0]` to be up to 59, while the union `i2c_smbus_data` only guarantees the size of `block` to be 34 bytes, as `I2C_SMBUS_BLOCK_MAX` is 32. This is the crux. So, whoever calls the I2C adapter expects the I2C adapter to not read/write more than `I2C_SMBUS_BLOCK_MAX` bytes from/to `data->block`. However, the driver in `ft260_smbus_xfer` does not check this. So it's exploitable as the call can pass a `union i2c_smbus_data` with a valid SMBUS block. The problem is `data->block[0]` is copied from userspace in `i2cdev_ioctl_smbus` and not checked. Therefore, a user can control the value passed to `data->block[0]` to be up to 59.
|
| 25/2 |
2026/05/30 15:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_ioctl_smbus]
Results: map[SourceCode: 319: static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
320: u8 read_write, u8 command, u32 size,
321: union i2c_smbus_data __user *data)
322: {
323: union i2c_smbus_data temp = {};
324: int datasize, res;
325:
326: if ((size != I2C_SMBUS_BYTE) &&
327: (size != I2C_SMBUS_QUICK) &&
328: (size != I2C_SMBUS_BYTE_DATA) &&
329: (size != I2C_SMBUS_WORD_DATA) &&
330: (size != I2C_SMBUS_PROC_CALL) &&
331: (size != I2C_SMBUS_BLOCK_DATA) &&
332: (size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
333: (size != I2C_SMBUS_I2C_BLOCK_DATA) &&
334: (size != I2C_SMBUS_BLOCK_PROC_CALL)) {
335: dev_dbg(&client->adapter->dev,
336: "size out of range (%x) in ioctl I2C_SMBUS.\n",
337: size);
338: return -EINVAL;
339: }
340: /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
341: so the check is valid if size==I2C_SMBUS_QUICK too. */
342: if ((read_write != I2C_SMBUS_READ) &&
343: (read_write != I2C_SMBUS_WRITE)) {
344: dev_dbg(&client->adapter->dev,
345: "read_write out of range (%x) in ioctl I2C_SMBUS.\n",
346: read_write);
347: return -EINVAL;
348: }
349:
350: /* Note that command values are always valid! */
351:
352: if ((size == I2C_SMBUS_QUICK) ||
353: ((size == I2C_SMBUS_BYTE) &&
354: (read_write == I2C_SMBUS_WRITE)))
355: /* These are special: we do not use data */
356: return i2c_smbus_xfer(client->adapter, client->addr,
357: client->flags, read_write,
358: command, size, NULL);
359:
360: if (data == NULL) {
361: dev_dbg(&client->adapter->dev,
362: "data is NULL pointer in ioctl I2C_SMBUS.\n");
363: return -EINVAL;
364: }
365:
366: if ((size == I2C_SMBUS_BYTE_DATA) ||
367: (size == I2C_SMBUS_BYTE))
368: datasize = sizeof(data->byte);
369: else if ((size == I2C_SMBUS_WORD_DATA) ||
370: (size == I2C_SMBUS_PROC_CALL))
371: datasize = sizeof(data->word);
372: else /* size == smbus block, i2c block, or block proc. call */
373: datasize = sizeof(data->block);
374:
375: if ((size == I2C_SMBUS_PROC_CALL) ||
376: (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
377: (size == I2C_SMBUS_I2C_BLOCK_DATA) ||
378: (read_write == I2C_SMBUS_WRITE)) {
379: if (copy_from_user(&temp, data, datasize))
380: return -EFAULT;
381: }
382: if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
383: /* Convert old I2C block commands to the new
384: convention. This preserves binary compatibility. */
385: size = I2C_SMBUS_I2C_BLOCK_DATA;
386: if (read_write == I2C_SMBUS_READ)
387: temp.block[0] = I2C_SMBUS_BLOCK_MAX;
388: }
389: res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
390: read_write, command, size, &temp);
391: if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
392: (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
393: (read_write == I2C_SMBUS_READ))) {
394: if (copy_to_user(data, &temp, datasize))
395: return -EFAULT;
396: }
397: return res;
398: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 26/2 |
2026/05/30 15:13 |
llm |
expert |
0m
Model:
Tokens:
input: 38602
output: 46
thoughts: 1252
Thoughts: **Vulnerability Analysis of I2C SMBus Block Data Transfer**
Okay, let's break this down. My initial assessment was that `i2cdev_ioctl_smbus` doesn't validate the block size (`temp.block[0]`) against `I2C_SMBUS_BLOCK_MAX`. It just blindly copies the user-provided `datasize` (34 bytes) into `temp` and then hands off `&temp` to `i2c_smbus_xfer`. This means `temp.block[0]` could be anything, potentially leading to out-of-bounds reads/writes if the underlying I2C adapter driver trusts it implicitly.
Specifically, with the `ft260_smbus_xfer` driver, it checks `data->block[0] + 1 < 60`. If `temp.block[0]` lands between 33 and 58, `ft260_smbus_xfer` tries to read up to 59 bytes from `temp`, which is only 34 bytes on the stack, resulting in a stack out-of-bounds read.
But then, I need to consider `read_write == I2C_SMBUS_READ`. Let's look at `ft260_smbus_xfer` in that case, specifically for `I2C_SMBUS_BLOCK_DATA` reads: It uses `data->block[0]`.
Hold on, for `I2C_SMBUS_READ`, `i2cdev_ioctl_smbus` doesn't copy `data` from userspace, which I need to check. Yes, it skips the `copy_from_user` call if it's `I2C_SMBUS_BLOCK_DATA` read. This means `temp` (used internally) is initialized to all zeros. Thus, `temp.block[0]` will be 0. So then, `ft260_i2c_read` will read 1 byte into `data->block`. No out-of-bounds write there.
But wait a minute, how about `I2C_SMBUS_I2C_BLOCK_DATA` reads? Okay, for *that* case, the code *does* copy from userspace!
Looking back to `ft260_smbus_xfer` with `I2C_SMBUS_I2C_BLOCK_DATA`, if `read_write == I2C_SMBUS_READ`, it passes `data->block[0]` to `ft260_i2c_read`. If `data->block[0]` is, say, 255, `ft260_i2c_read` will read up to 255 bytes into `data->block + 1`. Okay, but that's a *read* from the USB device, which means I should check `ft260_i2c_read` now to follow the data. I need to know if there's any boundary checking or other controls in the `ft260_i2c_read` function! That's the key to exploiting this potentially.
|
| 27/2 |
2026/05/30 15:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_i2c_read]
Results: map[SourceCode: 496: static int ft260_i2c_read(struct ft260_device *dev, u8 addr, u8 *data,
497: u16 len, u8 flag)
498: {
499: u16 rd_len;
500: u16 rd_data_max = 60;
501: int timeout, ret = 0;
502: struct ft260_i2c_read_request_report rep;
503: struct hid_device *hdev = dev->hdev;
504: u8 bus_busy = 0;
505:
506: if ((flag & FT260_FLAG_START_REPEATED) == FT260_FLAG_START_REPEATED)
507: flag = FT260_FLAG_START_REPEATED;
508: else
509: flag = FT260_FLAG_START;
510: do {
511: if (len <= rd_data_max) {
512: rd_len = len;
513: flag |= FT260_FLAG_STOP;
514: } else {
515: rd_len = rd_data_max;
516: }
517: rd_data_max = FT260_RD_DATA_MAX;
518:
519: rep.report = FT260_I2C_READ_REQ;
520: rep.length = cpu_to_le16(rd_len);
521: rep.address = addr;
522: rep.flag = flag;
523:
524: ft260_dbg("rep %#02x addr %#02x len %d rlen %d flag %#x\n",
525: rep.report, rep.address, len, rd_len, flag);
526:
527: reinit_completion(&dev->wait);
528:
529: dev->read_idx = 0;
530: dev->read_buf = data;
531: dev->read_len = rd_len;
532:
533: ret = ft260_hid_output_report(hdev, (u8 *)&rep, sizeof(rep));
534: if (ret < 0) {
535: hid_err(hdev, "%s: failed with %d\n", __func__, ret);
536: goto ft260_i2c_read_exit;
537: }
538:
539: timeout = msecs_to_jiffies(5000);
540: if (!wait_for_completion_timeout(&dev->wait, timeout)) {
541: ret = -ETIMEDOUT;
542: ft260_i2c_reset(hdev);
543: goto ft260_i2c_read_exit;
544: }
545:
546: dev->read_buf = NULL;
547:
548: if (flag & FT260_FLAG_STOP)
549: bus_busy = FT260_I2C_STATUS_BUS_BUSY;
550:
551: ret = ft260_xfer_status(dev, bus_busy);
552: if (ret < 0) {
553: ret = -EIO;
554: ft260_i2c_reset(hdev);
555: goto ft260_i2c_read_exit;
556: }
557:
558: len -= rd_len;
559: data += rd_len;
560: flag = 0;
561:
562: } while (len > 0);
563:
564: ft260_i2c_read_exit:
565: dev->read_buf = NULL;
566: return ret;
567: }
SourceFile:drivers/hid/hid-ft260.c]
|
| 28/2 |
2026/05/30 15:13 |
llm |
expert |
0m
Model:
Tokens:
input: 41786
output: 44
thoughts: 92
Thoughts: Okay, let's break this down.
**Diving into the FT260 I2C Read Operation**
Alright, so I'm looking at how the I2C read function works within the FT260 driver. My initial thought is to trace the data flow through `ft260_i2c_read`. It sets up the read buffer with `dev->read_buf = data;` and the length with `dev->read_len = rd_len;`. That makes sense; we're essentially telling the driver "Hey, here's where I want the data, and this is how much I need."
Then, we initiate the read request to the USB device and patiently wait. That part's standard for USB operations β we send the command and rely on the hardware to do its thing.
Here's where it gets interesting: the USB device's response. The key piece is that the data returned from the FT260 itself won't come directly back to our initial call of `ft260_i2c_read`. It's going to be handled by the HID raw event handler, which is, I'm assuming, `ft260_raw_event`. This is where the actual data transfer into the buffer pointed to by `dev->read_buf` occurs. So, my next logical step is to dive into how `ft260_raw_event` actually processes this raw data to get a clearer picture of that part of the process.
|
| 29/2 |
2026/05/30 15:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_raw_event]
Results: map[SourceCode:1065: static int ft260_raw_event(struct hid_device *hdev, struct hid_report *report,
1066: u8 *data, int size)
1067: {
1068: struct ft260_device *dev = hid_get_drvdata(hdev);
1069: struct ft260_i2c_input_report *xfer = (void *)data;
1070:
1071: if (xfer->report >= FT260_I2C_REPORT_MIN &&
1072: xfer->report <= FT260_I2C_REPORT_MAX) {
1073: ft260_dbg("i2c resp: rep %#02x len %d\n", xfer->report,
1074: xfer->length);
1075:
1076: if ((dev->read_buf == NULL) ||
1077: (xfer->length > dev->read_len - dev->read_idx)) {
1078: hid_err(hdev, "unexpected report %#02x, length %d\n",
1079: xfer->report, xfer->length);
1080: return -1;
1081: }
1082:
1083: memcpy(&dev->read_buf[dev->read_idx], &xfer->data,
1084: xfer->length);
1085: dev->read_idx += xfer->length;
1086:
1087: if (dev->read_idx == dev->read_len)
1088: complete(&dev->wait);
1089:
1090: } else {
1091: hid_err(hdev, "unhandled report %#02x\n", xfer->report);
1092: }
1093: return 0;
1094: }
SourceFile:drivers/hid/hid-ft260.c]
|
| 30/2 |
2026/05/30 15:13 |
llm |
expert |
0m
Model:
Tokens:
input: 42797
output: 46
thoughts: 885
Thoughts: **Analyzing a Potential Stack Buffer Overflow in I2C Communication**
Okay, let's break this down. My initial thought is that if `data->block[0]` in the `ft260_i2c_read` function is largeβsay, 255βthe `dev->read_len` variable gets repeatedly set to 60. Then, `ft260_raw_event` copies data from the USB device into `dev->read_buf`, which is pointing to `temp.block` located on the stack within `i2cdev_ioctl_smbus`. Since `temp.block` is only 34 bytes in size, this looks like a straightforward stack out-of-bounds write.
The key is in the `I2C_SMBUS_I2C_BLOCK_DATA` read operation: the code reads data based on the value in `data->block[0]`. Bingo! This value comes directly from userspace. So, userspace can control the length of the data being read and write potentially up to 255 bytes, which confirms a stack buffer overflow.
Now, can an unprivileged user exploit this? The vulnerability hinges on opening `/dev/i2c-X`. By default, this device is protected with root ownership and restrictive permissions. Typically, that means unprivileged users cannot directly access it.
However, the question becomes: is there another way to trigger `i2c_smbus_xfer` with a user-controlled `data->block[0]`? While other drivers may expose I2C functionality through sysfs, they typically hardcode the length. The core of the problem lies in the `ft260_smbus_xfer` function, which, I believe, doesn't adequately validate the size specified by `data->block[0]` against the defined `I2C_SMBUS_BLOCK_MAX`.
It's important to remember that `i2c_smbus_xfer` is an internal kernel API, but does it specify that `data->block[0]` can be greater than `I2C_SMBUS_BLOCK_MAX`? No! Looking at the `union i2c_smbus_data`, we see that `block` is defined as `block[I2C_SMBUS_BLOCK_MAX + 2]`. This means that, according to the API, `data->block[0]` MUST be less than or equal to `I2C_SMBUS_BLOCK_MAX` (32). If a caller is sending a larger value, then the caller is, in fact, buggy.
Then, looking back at `i2cdev_ioctl_smbus`, the code copies the `data` structure from userspace. Here's where the problem arises:
```c
if ((size == I2C_SMBUS_PROC_CALL) ||
(size == I2C_SMBUS_BLOCK_PROC_CALL) ||
(size == I2C_SMBUS_I2C_BLOCK_DATA) ||
(read_write == I2C_SMBUS_WRITE)) {
if (copy_from_user(&temp, data, datasize))
return -EFAULT;
}
```
My next task: Let's check `i2cdev_ioctl_smbus` again, carefully, to confirm whether or not it validates `temp.block[0]` against `I2C_SMBUS_BLOCK_MAX` after copying from userspace. If it doesn't, we have a clear path to exploitation via a classic stack buffer overflow.
|
| 31/2 |
2026/05/30 15:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-dev.c Name:i2cdev_ioctl_smbus]
Results: map[SourceCode: 319: static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
320: u8 read_write, u8 command, u32 size,
321: union i2c_smbus_data __user *data)
322: {
323: union i2c_smbus_data temp = {};
324: int datasize, res;
325:
326: if ((size != I2C_SMBUS_BYTE) &&
327: (size != I2C_SMBUS_QUICK) &&
328: (size != I2C_SMBUS_BYTE_DATA) &&
329: (size != I2C_SMBUS_WORD_DATA) &&
330: (size != I2C_SMBUS_PROC_CALL) &&
331: (size != I2C_SMBUS_BLOCK_DATA) &&
332: (size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
333: (size != I2C_SMBUS_I2C_BLOCK_DATA) &&
334: (size != I2C_SMBUS_BLOCK_PROC_CALL)) {
335: dev_dbg(&client->adapter->dev,
336: "size out of range (%x) in ioctl I2C_SMBUS.\n",
337: size);
338: return -EINVAL;
339: }
340: /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
341: so the check is valid if size==I2C_SMBUS_QUICK too. */
342: if ((read_write != I2C_SMBUS_READ) &&
343: (read_write != I2C_SMBUS_WRITE)) {
344: dev_dbg(&client->adapter->dev,
345: "read_write out of range (%x) in ioctl I2C_SMBUS.\n",
346: read_write);
347: return -EINVAL;
348: }
349:
350: /* Note that command values are always valid! */
351:
352: if ((size == I2C_SMBUS_QUICK) ||
353: ((size == I2C_SMBUS_BYTE) &&
354: (read_write == I2C_SMBUS_WRITE)))
355: /* These are special: we do not use data */
356: return i2c_smbus_xfer(client->adapter, client->addr,
357: client->flags, read_write,
358: command, size, NULL);
359:
360: if (data == NULL) {
361: dev_dbg(&client->adapter->dev,
362: "data is NULL pointer in ioctl I2C_SMBUS.\n");
363: return -EINVAL;
364: }
365:
366: if ((size == I2C_SMBUS_BYTE_DATA) ||
367: (size == I2C_SMBUS_BYTE))
368: datasize = sizeof(data->byte);
369: else if ((size == I2C_SMBUS_WORD_DATA) ||
370: (size == I2C_SMBUS_PROC_CALL))
371: datasize = sizeof(data->word);
372: else /* size == smbus block, i2c block, or block proc. call */
373: datasize = sizeof(data->block);
374:
375: if ((size == I2C_SMBUS_PROC_CALL) ||
376: (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
377: (size == I2C_SMBUS_I2C_BLOCK_DATA) ||
378: (read_write == I2C_SMBUS_WRITE)) {
379: if (copy_from_user(&temp, data, datasize))
380: return -EFAULT;
381: }
382: if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
383: /* Convert old I2C block commands to the new
384: convention. This preserves binary compatibility. */
385: size = I2C_SMBUS_I2C_BLOCK_DATA;
386: if (read_write == I2C_SMBUS_READ)
387: temp.block[0] = I2C_SMBUS_BLOCK_MAX;
388: }
389: res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
390: read_write, command, size, &temp);
391: if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
392: (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
393: (read_write == I2C_SMBUS_READ))) {
394: if (copy_to_user(data, &temp, datasize))
395: return -EFAULT;
396: }
397: return res;
398: }
SourceFile:drivers/i2c/i2c-dev.c]
|
| 32/2 |
2026/05/30 15:13 |
llm |
expert |
0m
Model:
Tokens:
input: 46041
output: 52
thoughts: 190
Thoughts: **Investigating the I2C SMBus Block Size Validation**
Okay, so I'm digging into this I2C SMBus code and I need to figure out *where* the block size limit is being enforced, if at all. My initial assumption, that `i2cdev_ioctl_smbus` would be the culprit, seems incorrect. I've re-examined the code and it definitely doesn't do the check `temp.block[0] <= I2C_SMBUS_BLOCK_MAX`. That's not the place.
Alright, the SMBus transfer flow goes through `i2c_smbus_xfer`, which in turn calls `__i2c_smbus_xfer`. So, I need to figure out if *either* of those functions validate the size. Let's start by re-inspecting `__i2c_smbus_xfer`. Hmm, it's just a wrapper! It calls `xfer_func(adapter, addr, flags, read_write, command, protocol, data)`. It simply dispatches to a transfer function and *doesn't* check the `data->block[0]` value itself.
Wait a second... it's the `xfer_func` that handles the actual SMBus transfer! What if `xfer_func` is the *emulated* transfer function, `i2c_smbus_xfer_emulated`? I need to check there. I need to know where the actual limit is enforced. I'll need to go deeper.
|
| 33/2 |
2026/05/30 15:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-smbus.c Name:i2c_smbus_xfer_emulated]
Results: map[SourceCode: 319: /*
320: * Simulate a SMBus command using the I2C protocol.
321: * No checking of parameters is done!
322: */
323: static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
324: unsigned short flags,
325: char read_write, u8 command, int size,
326: union i2c_smbus_data *data)
327: {
328: /*
329: * So we need to generate a series of msgs. In the case of writing, we
330: * need to use only one message; when reading, we need two. We
331: * initialize most things with sane defaults, to keep the code below
332: * somewhat simpler.
333: */
334: unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
335: unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
336: int nmsgs = read_write == I2C_SMBUS_READ ? 2 : 1;
337: u8 partial_pec = 0;
338: int status;
339: struct i2c_msg msg[2] = {
340: {
341: .addr = addr,
342: .flags = flags,
343: .len = 1,
344: .buf = msgbuf0,
345: }, {
346: .addr = addr,
347: .flags = flags | I2C_M_RD,
348: .len = 0,
349: .buf = msgbuf1,
350: },
351: };
352: bool wants_pec = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
353: && size != I2C_SMBUS_I2C_BLOCK_DATA);
354:
355: msgbuf0[0] = command;
356: switch (size) {
357: case I2C_SMBUS_QUICK:
358: msg[0].len = 0;
359: /* Special case: The read/write field is used as data */
360: msg[0].flags = flags | (read_write == I2C_SMBUS_READ ?
361: I2C_M_RD : 0);
362: nmsgs = 1;
363: break;
364: case I2C_SMBUS_BYTE:
365: if (read_write == I2C_SMBUS_READ) {
366: /* Special case: only a read! */
367: msg[0].flags = I2C_M_RD | flags;
368: nmsgs = 1;
369: }
370: break;
371: case I2C_SMBUS_BYTE_DATA:
372: if (read_write == I2C_SMBUS_READ)
373: msg[1].len = 1;
374: else {
375: msg[0].len = 2;
376: msgbuf0[1] = data->byte;
377: }
378: break;
379: case I2C_SMBUS_WORD_DATA:
380: if (read_write == I2C_SMBUS_READ)
381: msg[1].len = 2;
382: else {
383: msg[0].len = 3;
384: msgbuf0[1] = data->word & 0xff;
385: msgbuf0[2] = data->word >> 8;
386: }
387: break;
388: case I2C_SMBUS_PROC_CALL:
389: nmsgs = 2; /* Special case */
390: read_write = I2C_SMBUS_READ;
391: msg[0].len = 3;
392: msg[1].len = 2;
393: msgbuf0[1] = data->word & 0xff;
394: msgbuf0[2] = data->word >> 8;
395: break;
396: case I2C_SMBUS_BLOCK_DATA:
397: if (read_write == I2C_SMBUS_READ) {
398: msg[1].flags |= I2C_M_RECV_LEN;
399: msg[1].len = 1; /* block length will be added by
400: the underlying bus driver */
401: i2c_smbus_try_get_dmabuf(&msg[1], 0);
402: } else {
403: msg[0].len = data->block[0] + 2;
404: if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {
405: dev_err(&adapter->dev,
406: "Invalid block write size %d\n",
407: data->block[0]);
408: return -EINVAL;
409: }
410:
411: i2c_smbus_try_get_dmabuf(&msg[0], command);
412: memcpy(msg[0].buf + 1, data->block, msg[0].len - 1);
413: }
414: break;
415: case I2C_SMBUS_BLOCK_PROC_CALL:
416: nmsgs = 2; /* Another special case */
417: read_write = I2C_SMBUS_READ;
418: if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
419: dev_err(&adapter->dev,
420: "Invalid block write size %d\n",
421: data->block[0]);
422: return -EINVAL;
423: }
424:
425: msg[0].len = data->block[0] + 2;
426: i2c_smbus_try_get_dmabuf(&msg[0], command);
427: memcpy(msg[0].buf + 1, data->block, msg[0].len - 1);
428:
429: msg[1].flags |= I2C_M_RECV_LEN;
430: msg[1].len = 1; /* block length will be added by
431: the underlying bus driver */
432: i2c_smbus_try_get_dmabuf(&msg[1], 0);
433: break;
434: case I2C_SMBUS_I2C_BLOCK_DATA:
435: if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
436: dev_err(&adapter->dev, "Invalid block %s size %d\n",
437: str_read_write(read_write == I2C_SMBUS_READ),
438: data->block[0]);
439: return -EINVAL;
440: }
441:
442: if (read_write == I2C_SMBUS_READ) {
443: msg[1].len = data->block[0];
444: i2c_smbus_try_get_dmabuf(&msg[1], 0);
445: } else {
446: msg[0].len = data->block[0] + 1;
447:
448: i2c_smbus_try_get_dmabuf(&msg[0], command);
449: memcpy(msg[0].buf + 1, data->block + 1, data->block[0]);
450: }
451: break;
452: default:
453: dev_err(&adapter->dev, "Unsupported transaction %d\n", size);
454: return -EOPNOTSUPP;
455: }
456:
457: if (wants_pec) {
458: /* Compute PEC if first message is a write */
459: if (!(msg[0].flags & I2C_M_RD)) {
460: if (nmsgs == 1) /* Write only */
461: i2c_smbus_add_pec(&msg[0]);
462: else /* Write followed by read */
463: partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
464: }
465: /* Ask for PEC if last message is a read */
466: if (msg[nmsgs - 1].flags & I2C_M_RD)
467: msg[nmsgs - 1].len++;
468: }
469:
470: status = __i2c_transfer(adapter, msg, nmsgs);
471: if (status < 0)
472: goto cleanup;
473: if (status != nmsgs) {
474: status = -EIO;
475: goto cleanup;
476: }
477: status = 0;
478:
479: /* Check PEC if last message is a read */
480: if (wants_pec && (msg[nmsgs - 1].flags & I2C_M_RD)) {
481: status = i2c_smbus_check_pec(partial_pec, &msg[nmsgs - 1]);
482: if (status < 0)
483: goto cleanup;
484: }
485:
486: if (read_write == I2C_SMBUS_READ)
487: switch (size) {
488: case I2C_SMBUS_BYTE:
489: data->byte = msgbuf0[0];
490: break;
491: case I2C_SMBUS_BYTE_DATA:
492: data->byte = msgbuf1[0];
493: break;
494: case I2C_SMBUS_WORD_DATA:
495: case I2C_SMBUS_PROC_CALL:
496: data->word = msgbuf1[0] | (msgbuf1[1] << 8);
497: break;
498: case I2C_SMBUS_I2C_BLOCK_DATA:
499: memcpy(data->block + 1, msg[1].buf, data->block[0]);
500: break;
501: case I2C_SMBUS_BLOCK_DATA:
502: case I2C_SMBUS_BLOCK_PROC_CALL:
503: if (msg[1].buf[0] > I2C_SMBUS_BLOCK_MAX) {
504: dev_err(&adapter->dev,
505: "Invalid block size returned: %d\n",
506: msg[1].buf[0]);
507: status = -EPROTO;
508: goto cleanup;
509: }
510: memcpy(data->block, msg[1].buf, msg[1].buf[0] + 1);
511: break;
512: }
513:
514: cleanup:
515: if (msg[0].flags & I2C_M_DMA_SAFE)
516: kfree(msg[0].buf);
517: if (msg[1].flags & I2C_M_DMA_SAFE)
518: kfree(msg[1].buf);
519:
520: return status;
521: }
SourceFile:drivers/i2c/i2c-core-smbus.c]
|
| 34/2 |
2026/05/30 15:13 |
llm |
expert |
0m
Model:
Tokens:
input: 50019
output: 31
thoughts: 507
Thoughts: **Analyzing SMBus Block Data Validation**
Okay, so I'm diving into this SMBus transfer code, specifically focusing on block data transfers. I see that `i2c_smbus_xfer_emulated` *does* perform a crucial check on `data->block[0]`, ensuring it doesn't exceed `I2C_SMBUS_BLOCK_MAX`. That's good; it validates the block size, which is critical for preventing buffer overflows or other issues. I saw it in two places, for `I2C_SMBUS_BLOCK_DATA` and `I2C_SMBUS_I2C_BLOCK_DATA`, confirming this behavior.
However, the `ft260_smbus_xfer` function, part of the `ft260` driver, is a custom SMBus transfer implementation. And now I see that it's *not* validating `data->block[0]` against that same upper bound of `I2C_SMBUS_BLOCK_MAX`. Instead, it seems to only check the buffer size against the driver internal data structure's buffer size. This is a problem! It relies on the assumption that the `data->block[0]` value has been validated upstream.
This is a subtle but potentially serious bug. The core `i2c_smbus_xfer` function doesn't enforce this limit, so any custom `smbus_xfer` function absolutely *must* perform this check itself. The driver assumes that the block size, as indicated by `data->block[0]`, is properly constrained. This isn't necessarily true. I need to make sure this validation happens in all custom `smbus_xfer` implementations. I need to investigate other custom driver implementations to see if they're making the same mistake.
|
| 35/2 |
2026/05/30 15:13 |
tool |
grepper |
0m
Args: map[Expression:data->block\[0\] > I2C_SMBUS_BLOCK_MAX]
Results: map[Output:drivers/hid/hid-cp2112.c=629=static int cp2112_xfer(struct i2c_adapter *adap, u16 addr,
--
drivers/hid/hid-cp2112.c-695-
drivers/hid/hid-cp2112.c:696: if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
drivers/hid/hid-cp2112.c-697- count = -EINVAL;
--
drivers/hid/hid-cp2112.c-713- */
drivers/hid/hid-cp2112.c:714: if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
drivers/hid/hid-cp2112.c-715- count = -EINVAL;
--
drivers/hid/hid-cp2112.c-729- */
drivers/hid/hid-cp2112.c:730: if (data->block[0] > I2C_SMBUS_BLOCK_MAX + 1)
drivers/hid/hid-cp2112.c-731- count = -EINVAL;
--
drivers/i2c/busses/i2c-i801.c=826=static int i801_smbus_block_transaction(struct i801_priv *priv, union i2c_smbus_data *data,
--
drivers/i2c/busses/i2c-i801.c-831- data->block[0] = SMBUS_LEN_SENTINEL;
drivers/i2c/busses/i2c-i801.c:832: else if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-i801.c-833- return -EPROTO;
--
drivers/i2c/busses/i2c-i801.c=848=static int i801_i2c_block_transaction(struct i801_priv *priv, union i2c_smbus_data *data,
--
drivers/i2c/busses/i2c-i801.c-853-
drivers/i2c/busses/i2c-i801.c:854: if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-i801.c-855- return -EPROTO;
--
drivers/i2c/busses/i2c-isch.c=159=static s32 sch_access(struct i2c_adapter *adap, u16 addr,
--
drivers/i2c/busses/i2c-isch.c-252- data->block[0] = sch_io_rd8(priv, SMBHSTDAT0);
drivers/i2c/busses/i2c-isch.c:253: if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-isch.c-254- return -EPROTO;
--
drivers/i2c/busses/i2c-ismt.c=403=static int ismt_access(struct i2c_adapter *adap, u16 addr,
--
drivers/i2c/busses/i2c-ismt.c-520- dev_dbg(dev, "I2C_SMBUS_BLOCK_DATA: WRITE\n");
drivers/i2c/busses/i2c-ismt.c:521: if (data->block[0] < 1 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-ismt.c-522- return -EINVAL;
--
drivers/i2c/busses/i2c-ismt.c-542- dev_dbg(dev, "I2C_SMBUS_BLOCK_PROC_CALL\n");
drivers/i2c/busses/i2c-ismt.c:543: if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-ismt.c-544- return -EINVAL;
--
drivers/i2c/busses/i2c-ismt.c-560-
drivers/i2c/busses/i2c-ismt.c:561: if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-ismt.c-562- data->block[0] = I2C_SMBUS_BLOCK_MAX;
--
drivers/i2c/busses/i2c-piix4.c=597=static s32 piix4_access(struct i2c_adapter * adap, u16 addr,
--
drivers/i2c/busses/i2c-piix4.c-676- data->block[0] = inb_p(SMBHSTDAT0);
drivers/i2c/busses/i2c-piix4.c:677: if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-piix4.c-678- return -EPROTO;
--
drivers/i2c/busses/i2c-stm32f7.c=982=static int stm32f7_i2c_smbus_xfer_msg(struct stm32f7_i2c_dev *i2c_dev,
--
drivers/i2c/busses/i2c-stm32f7.c-1046- f7_msg->stop = true;
drivers/i2c/busses/i2c-stm32f7.c:1047: if (data->block[0] > I2C_SMBUS_BLOCK_MAX ||
drivers/i2c/busses/i2c-stm32f7.c-1048- !data->block[0]) {
--
drivers/i2c/busses/i2c-stm32f7.c-1067- f7_msg->stop = false;
drivers/i2c/busses/i2c-stm32f7.c:1068: if (data->block[0] > I2C_SMBUS_BLOCK_MAX - 1) {
drivers/i2c/busses/i2c-stm32f7.c-1069- dev_err(dev, "Invalid block write size %d\n",
--
drivers/i2c/busses/i2c-viapro.c=190=static s32 vt596_access(struct i2c_adapter *adap, u16 addr,
--
drivers/i2c/busses/i2c-viapro.c-273- data->block[0] = inb_p(SMBHSTDAT0);
drivers/i2c/busses/i2c-viapro.c:274: if (data->block[0] > I2C_SMBUS_BLOCK_MAX)
drivers/i2c/busses/i2c-viapro.c-275- data->block[0] = I2C_SMBUS_BLOCK_MAX;
--
drivers/i2c/i2c-core-smbus.c=323=static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
--
drivers/i2c/i2c-core-smbus.c-417- read_write = I2C_SMBUS_READ;
drivers/i2c/i2c-core-smbus.c:418: if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
drivers/i2c/i2c-core-smbus.c-419- dev_err(&adapter->dev,
--
drivers/i2c/i2c-core-smbus.c-434- case I2C_SMBUS_I2C_BLOCK_DATA:
drivers/i2c/i2c-core-smbus.c:435: if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
drivers/i2c/i2c-core-smbus.c-436- dev_err(&adapter->dev, "Invalid block %s size %d\n",
]
|
| 36/2 |
2026/05/30 15:13 |
llm |
expert |
0m
Model:
Tokens:
input: 53212
output: 45
thoughts: 316
Thoughts: **My Assessment of the FT260 SMBus Vulnerability**
Okay, so I've been digging into this, and it's clear that the `ft260_smbus_xfer` function is definitely flawed. I see a lot of driver implementations out there that correctly check `data->block[0] > I2C_SMBUS_BLOCK_MAX`, but this one, unfortunately, doesn't. That's a serious oversight.
Now, the real question is, how bad is it? Let's break down the exploitability and the potential triggers. Yes, definitely exploitable. We're looking at a stack out-of-bounds read and write here. That read could leak sensitive stack memory straight to the USB device, which is never a good thing. And that write? Well, the write is much worse. We can overwrite the stack of the `i2cdev_ioctl_smbus` function with data from the USB device! Thinking through the `i2cdev_ioctl_smbus` stack frame, I recall there's a `temp` variable (34 bytes), that the maximum number of bytes we can write up to is 255. That means we can overwrite the return address of `i2cdev_ioctl_smbus`! That's a textbook stack buffer overflow, leading to, potentially, arbitrary code execution. Definitely exploitable.
Denial of Service is also a very real possibility. A stack buffer overflow like this will almost certainly trigger a kernel panic.
Finally, we need to consider accessibility. To trigger this, a user would need to open a `/dev/i2c-X` device and call the `I2C_SMBUS` ioctl. The critical question here is whether unprivileged processes can access those devices. My understanding is that `/dev/i2c-X` is typically created by `i2c-dev` with default permissions. I should probably check the `i2c-dev.c` source code quickly to make sure there are no custom permissions in place, but I would assume that unprivileged processes can access this device.
|
| 37/2 |
2026/05/30 15:14 |
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]
|
| 38/2 |
2026/05/30 15:14 |
llm |
expert |
0m
Model:
Tokens:
input: 54711
output: 52
thoughts: 539
Thoughts: **Analyzing I2C Device Access and Potential Security Implications**
Okay, let's break this down. From what I'm seeing, the `i2c-dev.c` driver doesn't implement any custom permissions. By default, `cdev_device_add` creates the device node with standard root:root ownership and permissions of 0600. That means a typical unprivileged user won't be able to access it directly, unless udev rules are in place. Often, udev rules, like those from systemd, will change this to 0660 root:i2c, granting access to members of the `i2c` group. This setup suggests that I'd need to be part of that group to directly interact with the I2C device.
But, hang on, is there another way to trigger the `ft260_smbus_xfer` function? My initial thought is the most common use case β what if another kernel driver is using this I2C adapter? If a driver is communicating through I2C, it's going to use the `i2c_smbus_xfer`, `i2c_smbus_read_block_data`, or similar functions, right? These drivers are going to pass a valid `union i2c_smbus_data` structure, properly initializing `block[0]`, or they will expect the adapter to handle it. In the read case, the kernel driver passes an empty `data` and the adapter fills in `data->block[0]` and other data.
Okay, let's focus on the `i2c_smbus_read_block_data` operation. Specifically, looking at the code, when a kernel driver does a block read, `ft260_smbus_xfer` handles it this way:
```c
if (read_write == I2C_SMBUS_READ) {
ret = ft260_smbus_write(dev, addr, cmd, NULL, 0,
FT260_FLAG_START);
if (ret)
goto smbus_exit;
ret = ft260_i2c_read(dev, addr, data->block,
data->block[0] + 1,
FT260_FLAG_START_STOP_REPEATED);
}
```
Now, that's interesting! For `I2C_SMBUS_BLOCK_DATA` reads, the value of `data->block[0]` is supposed to represent the length of the block. But the length for a *read* is actually returned by the device. The caller doesn't actually know the length beforehand. Usually, the caller sets `data->block[0] = 0` or leaves it uninitialized. If it's uninitialized... it could be a large value! This could be a potential issue! I need to investigate how `i2c_smbus_read_block_data` is actually implemented, to understand if this is exploitable.
|
| 39/2 |
2026/05/30 15:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-smbus.c Name:i2c_smbus_read_block_data]
Results: map[SourceCode: 210: /**
211: * i2c_smbus_read_block_data - SMBus "block read" protocol
212: * @client: Handle to slave device
213: * @command: Byte interpreted by slave
214: * @values: Byte array into which data will be read; big enough to hold
215: * the data returned by the slave. SMBus allows at most 32 bytes.
216: *
217: * This executes the SMBus "block read" protocol, returning negative errno
218: * else the number of data bytes in the slave's response.
219: *
220: * Note that using this function requires that the client's adapter support
221: * the I2C_FUNC_SMBUS_READ_BLOCK_DATA functionality. Not all adapter drivers
222: * support this; its emulation through I2C messaging relies on a specific
223: * mechanism (I2C_M_RECV_LEN) which may not be implemented.
224: */
225: s32 i2c_smbus_read_block_data(const struct i2c_client *client, u8 command,
226: u8 *values)
227: {
228: union i2c_smbus_data data;
229: int status;
230:
231: status = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
232: I2C_SMBUS_READ, command,
233: I2C_SMBUS_BLOCK_DATA, &data);
234: if (status)
235: return status;
236:
237: memcpy(values, &data.block[1], data.block[0]);
238: return data.block[0];
239: }
SourceFile:drivers/i2c/i2c-core-smbus.c]
|
| 40/2 |
2026/05/30 15:14 |
llm |
expert |
0m
Model:
Tokens:
input: 56530
output: 46
thoughts: 1296
Thoughts: **Vulnerability Assessment: Uninitialized Stack Memory in FT260 I2C Driver**
Okay, let's break this down. My initial thought is focused on `i2c_smbus_read_block_data`. I see a potential problem right away: the `union i2c_smbus_data data;` is declared on the stack without initialization. That's a red flag. This means `data.block[0]` contains uninitialized stack memory.
Then, the code calls `i2c_smbus_xfer`, and it's passing this uninitialized `data` structure to it. Diving into `ft260_smbus_xfer`, specifically the `I2C_SMBUS_BLOCK_DATA` case, it's clear the uninitialized value is being used for determining the read length. The code reads from the device using `data->block[0] + 1` as the length. This is where it gets scary. Because `data.block[0]` is uninitialized, it could hold any value, potentially a large one. If that happens, `ft260_i2c_read` will attempt to read a number of bytes that far exceeds the size of the `data` union on the stack β 34 bytes! A clear stack buffer overflow.
I need to confirm this. I'm thinking, is this correct? It appears there is a bug here, where `ft260_smbus_xfer` is misusing the byte read from the I2C device. For `I2C_SMBUS_BLOCK_DATA` reads, the device itself provides the length of the block data in the first byte of the response. The I2C core handles this correctly with `I2C_M_RECV_LEN`. However, `ft260_smbus_xfer` is using an uninitialized input parameter.
To confirm my understanding, I'll review `i2c_smbus_xfer_emulated`. Bingo! The core I2C code *correctly* handles `I2C_SMBUS_BLOCK_DATA` reads, knowing the length comes from the device. But `ft260_smbus_xfer` is completely off, it's using the uninitialized `data->block[0]` as the input length.
Okay, this is a serious issue. Any kernel driver that calls `i2c_smbus_read_block_data` on an FT260 adapter is vulnerable. The size of the overflow depends on the value of the uninitialized byte.
Now, the next question is exploitability. Can an unprivileged user trigger this? If a device connected to the FT260 I2C bus is exposed to userspace (e.g., via hwmon or an EEPROM), reading from sysfs could trigger the vulnerable code path. The FT260 is a USB-to-I2C bridge. An attacker could potentially insert a malicious FT260 device that emulates an I2C device. The kernel might probe I2C drivers on it, but is this likely without explicit setup? I2C devices aren't usually probed automatically unless instantiated explicitly via sysfs or ACPI/DT.
What about DDC/CI for monitors? If the FT260 connects a monitor, `i2c-dev` or `ddcci` could come into play. But `i2c-dev` is root-restricted. So, no direct attack vector here from unprivileged users.
Can unprivileged processes trigger `i2c_smbus_read_block_data` on arbitrary I2C adapters? No, not without access to a driver that uses it. No easy route to a remote exploit from unprivileged processes.
Next, I need to look at the USB side. A malicious USB device emulating an FT260 could control responses. It couldn't *trigger* the I2C transfer unless a process initiated it. If an attacker has physical access to plug in the USB device, they might be able to find a way to start a process, even as an unprivileged user, but then again, they can't simply open `/dev/i2c-X`.
Is there automatic probing that could trigger `I2C_SMBUS_BLOCK_DATA` reads? I'll review `i2c_register_adapter` to verify. I need to be sure.
My initial assessment is that this is a *local* privilege escalation vulnerability. The attacker needs physical access to the device and the ability to trigger the affected function, but they'd likely need root to perform a full exploit. However, the stack overflow is clear, and the bug lies in incorrect handling of the read length from the device.
|
| 41/2 |
2026/05/30 15:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_register_adapter]
Results: map[SourceCode:1519: static int i2c_register_adapter(struct i2c_adapter *adap)
1520: {
1521: int res = -EINVAL;
1522:
1523: /* Can't register until after driver model init */
1524: if (WARN_ON(!is_registered)) {
1525: res = -EAGAIN;
1526: goto out_list;
1527: }
1528:
1529: /* Sanity checks */
1530: if (WARN(!adap->name[0], "i2c adapter has no name"))
1531: goto out_list;
1532:
1533: if (!adap->algo) {
1534: pr_err("adapter '%s': no algo supplied!\n", adap->name);
1535: goto out_list;
1536: }
1537:
1538: if (!adap->lock_ops)
1539: adap->lock_ops = &i2c_adapter_lock_ops;
1540:
1541: adap->locked_flags = 0;
1542: rt_mutex_init(&adap->bus_lock);
1543: rt_mutex_init(&adap->mux_lock);
1544: mutex_init(&adap->userspace_clients_lock);
1545: INIT_LIST_HEAD(&adap->userspace_clients);
1546:
1547: /* Set default timeout to 1 second if not already set */
1548: if (adap->timeout == 0)
1549: adap->timeout = HZ;
1550:
1551: /* register soft irqs for Host Notify */
1552: res = i2c_setup_host_notify_irq_domain(adap);
1553: if (res) {
1554: pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n",
1555: adap->name, res);
1556: goto out_list;
1557: }
1558:
1559: dev_set_name(&adap->dev, "i2c-%d", adap->nr);
1560: adap->dev.bus = &i2c_bus_type;
1561: adap->dev.type = &i2c_adapter_type;
1562: device_initialize(&adap->dev);
1563:
1564: /*
1565: * This adapter can be used as a parent immediately after device_add(),
1566: * setup runtime-pm (especially ignore-children) before hand.
1567: */
1568: device_enable_async_suspend(&adap->dev);
1569: pm_runtime_no_callbacks(&adap->dev);
1570: pm_suspend_ignore_children(&adap->dev, true);
1571: pm_runtime_enable(&adap->dev);
1572:
1573: res = device_add(&adap->dev);
1574: if (res) {
1575: pr_err("adapter '%s': can't register device (%d)\n", adap->name, res);
1576: put_device(&adap->dev);
1577: goto out_list;
1578: }
1579:
1580: adap->debugfs = debugfs_create_dir(dev_name(&adap->dev), i2c_debugfs_root);
1581:
1582: res = i2c_setup_smbus_alert(adap);
1583: if (res)
1584: goto out_reg;
1585:
1586: res = i2c_init_recovery(adap);
1587: if (res == -EPROBE_DEFER)
1588: goto out_reg;
1589:
1590: dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
1591:
1592: /* create pre-declared device nodes */
1593: of_i2c_register_devices(adap);
1594: i2c_acpi_install_space_handler(adap);
1595: i2c_acpi_register_devices(adap);
1596:
1597: if (adap->nr < __i2c_first_dynamic_bus_num)
1598: i2c_scan_static_board_info(adap);
1599:
1600: /* Notify drivers */
1601: mutex_lock(&core_lock);
1602: bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
1603: mutex_unlock(&core_lock);
1604:
1605: return 0;
1606:
1607: out_reg:
1608: debugfs_remove_recursive(adap->debugfs);
1609: init_completion(&adap->dev_released);
1610: device_unregister(&adap->dev);
1611: wait_for_completion(&adap->dev_released);
1612: out_list:
1613: mutex_lock(&core_lock);
1614: idr_remove(&i2c_adapter_idr, adap->nr);
1615: mutex_unlock(&core_lock);
1616: return res;
1617: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 42/2 |
2026/05/30 15:14 |
llm |
expert |
0m
Model:
Tokens:
input: 60440
output: 44
thoughts: 85
Thoughts: Here's my analysis of that code snippet:
**I2C Adapter Detection Workflow**
Okay, so I'm looking at this code and breaking down how I2C adapters and drivers are initialized and discovered. My thought process is centered around how this `bus_for_each_drv` function works with I2C. It seems that this function, called with the `i2c_bus_type`, is iterating through the registered I2C drivers. The `NULL` argument suggests some default behaviour, and `adap` is likely the I2C adapter being considered. Crucially, the fourth argument, `__process_new_adapter`, is the core function called for each registered I2C driver.
Now, this `__process_new_adapter` call is what triggers the core detection logic. Essentially, it looks like `i2c_do_add_adapter` is the underlying function being executed. This is what's going to handle the actual attachment of the adapter. My hunch is that within that, if an I2C driver has implemented a `detect` function, it's that function's responsibility to scan the adapter for devices. That is, the `detect` function provides a hook for each driver to probe the bus. So, to really understand this flow, I need to check how `i2c_detect` is actually implemented, and see what the driver writer can do there. This is where the rubber meets the road.
|
| 43/2 |
2026/05/30 15:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_detect]
Results: map[SourceCode:2508: static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
2509: {
2510: const unsigned short *address_list;
2511: struct i2c_client *temp_client;
2512: int i, err = 0;
2513:
2514: address_list = driver->address_list;
2515: if (!driver->detect || !address_list)
2516: return 0;
2517:
2518: /* Warn that the adapter lost class based instantiation */
2519: if (adapter->class == I2C_CLASS_DEPRECATED) {
2520: dev_dbg(&adapter->dev,
2521: "This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
2522: "If you need it, check 'Documentation/i2c/instantiating-devices.rst' for alternatives.\n",
2523: driver->driver.name);
2524: return 0;
2525: }
2526:
2527: /* Stop here if the classes do not match */
2528: if (!(adapter->class & driver->class))
2529: return 0;
2530:
2531: /* Set up a temporary client to help detect callback */
2532: temp_client = kzalloc(sizeof(*temp_client), GFP_KERNEL);
2533: if (!temp_client)
2534: return -ENOMEM;
2535:
2536: temp_client->adapter = adapter;
2537:
2538: for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
2539: dev_dbg(&adapter->dev,
2540: "found normal entry for adapter %d, addr 0x%02x\n",
2541: i2c_adapter_id(adapter), address_list[i]);
2542: temp_client->addr = address_list[i];
2543: err = i2c_detect_address(temp_client, driver);
2544: if (unlikely(err))
2545: break;
2546: }
2547:
2548: kfree(temp_client);
2549:
2550: return err;
2551: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 44/2 |
2026/05/30 15:14 |
llm |
expert |
0m
Model:
Tokens:
input: 61592
output: 42
thoughts: 117
Thoughts: **Analyzing I2C Address Detection**
Okay, so `i2c_detect_address` is the key function here. It kicks things off by first using `i2c_check_7bit_addr_validity_strict` to validate the address, which makes sense. Then, it proceeds to probe for a device, either via `i2c_default_probe` or the driver-specific `driver->detect` function.
Now, `i2c_default_probe` seems straightforward β itβs using either an `I2C_SMBUS_QUICK` write or read operation to check for a response. No surprises there. If the driver has provided its own `detect` routine, it *might* involve SMBus reads as well. So far, so good.
But hang on, there's a possible gotcha. I need to specifically look at the `ft260` adapter's implementation. The text mentions that this particular adapter *doesn't* set `adapter->class`. I should investigate the `ft260_probe` function to understand why this could be an issue and how it might influence address detection. I need to see how the adapter interacts with the bus and if the lack of `adapter->class` leads to a different approach.
|
| 45/2 |
2026/05/30 15:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_probe]
Results: map[SourceCode: 960: static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
961: {
962: struct ft260_device *dev;
963: struct ft260_get_chip_version_report version;
964: int ret;
965:
966: if (!hid_is_usb(hdev))
967: return -EINVAL;
968:
969: dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
970: if (!dev)
971: return -ENOMEM;
972:
973: ret = hid_parse(hdev);
974: if (ret) {
975: hid_err(hdev, "failed to parse HID\n");
976: return ret;
977: }
978:
979: ret = hid_hw_start(hdev, 0);
980: if (ret) {
981: hid_err(hdev, "failed to start HID HW\n");
982: return ret;
983: }
984:
985: ret = hid_hw_open(hdev);
986: if (ret) {
987: hid_err(hdev, "failed to open HID HW\n");
988: goto err_hid_stop;
989: }
990:
991: ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
992: (u8 *)&version, sizeof(version));
993: if (ret < 0) {
994: hid_err(hdev, "failed to retrieve chip version\n");
995: goto err_hid_close;
996: }
997:
998: hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
999: version.chip_code[0], version.chip_code[1],
1000: version.chip_code[2], version.chip_code[3]);
1001:
1002: ret = ft260_is_interface_enabled(hdev);
1003: if (ret <= 0)
1004: goto err_hid_close;
1005:
1006: hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
1007: hdev->version >> 8, hdev->version & 0xff, hdev->name,
1008: hdev->phys);
1009:
1010: hid_set_drvdata(hdev, dev);
1011: dev->hdev = hdev;
1012: dev->adap.owner = THIS_MODULE;
1013: dev->adap.class = I2C_CLASS_HWMON;
1014: dev->adap.algo = &ft260_i2c_algo;
1015: dev->adap.quirks = &ft260_i2c_quirks;
1016: dev->adap.dev.parent = &hdev->dev;
1017: snprintf(dev->adap.name, sizeof(dev->adap.name),
1018: "FT260 usb-i2c bridge");
1019:
1020: mutex_init(&dev->lock);
1021: init_completion(&dev->wait);
1022:
1023: ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
1024: if (ret)
1025: ft260_i2c_reset(hdev);
1026:
1027: i2c_set_adapdata(&dev->adap, dev);
1028: ret = i2c_add_adapter(&dev->adap);
1029: if (ret) {
1030: hid_err(hdev, "failed to add i2c adapter\n");
1031: goto err_hid_close;
1032: }
1033:
1034: ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
1035: if (ret < 0) {
1036: hid_err(hdev, "failed to create sysfs attrs\n");
1037: goto err_i2c_free;
1038: }
1039:
1040: return 0;
1041:
1042: err_i2c_free:
1043: i2c_del_adapter(&dev->adap);
1044: err_hid_close:
1045: hid_hw_close(hdev);
1046: err_hid_stop:
1047: hid_hw_stop(hdev);
1048: return ret;
1049: }
SourceFile:drivers/hid/hid-ft260.c]
|
| 46/2 |
2026/05/30 15:14 |
llm |
expert |
0m
Model:
Tokens:
input: 63494
output: 28
thoughts: 155
Thoughts: **I2C Adapter Discovery and HWMON Drivers**
Okay, so I've spotted this line of code: `dev->adap.class = I2C_CLASS_HWMON;`. And that's pretty clever. Setting the adapter class to `I2C_CLASS_HWMON` means that the hardware monitoring drivers are going to automatically probe this adapter! When `i2c_add_adapter` is executed, it's going to trigger the `i2c_detect` function for any I2C driver that's got `I2C_CLASS_HWMON` set.
A lot of the hardware monitoring drivers utilize a `detect` function to read from the I2C bus and figure out if a specific chip is present. Hmm, thatβs where things get interesting...I'm wondering, are any of these hwmon drivers using `i2c_smbus_read_block_data` within their `detect` functions? It would be good to check because it would save time and effort. I need to investigate.
|
| 47/2 |
2026/05/30 15:14 |
tool |
grepper |
0m
Error:
Args: map[Expression:i2c_smbus_read_block_data.*detect]
Results:
|
| 48/2 |
2026/05/30 15:14 |
llm |
expert |
0m
Model:
Tokens:
input: 63918
output: 37
thoughts: 28
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating i2c_smbus_read_block_data in hwmon**
Right, let's cut the fluff and get straight to the point. When I'm looking for how a specific I2C SMBus function like `i2c_smbus_read_block_data` is used within the hwmon driver subsystem, the most efficient approach is a targeted code search. I don't need to sift through irrelevant documentation or try and piece together usage from high-level descriptions. The quickest route to understanding the practical implementation is to dive directly into the code. So, let's start with a focused search, specifically within the `drivers/hwmon/` directory, for exactly that function name: `i2c_smbus_read_block_data`. This immediately gives me the context I need. From there, I can examine the call sites, see the data structures being passed, and quickly grasp how it's being integrated into specific sensor or monitoring device drivers. This targeted search is far more productive than a general keyword search.
|
| 49/2 |
2026/05/30 15:15 |
tool |
grepper |
0m
Args: map[Expression:i2c_smbus_read_block_data PathPrefix:drivers/hwmon/]
Results: map[Output:drivers/hwmon/lineage-pem.c=130=static int pem_read_block(struct i2c_client *client, u8 command, u8 *data,
--
drivers/hwmon/lineage-pem.c-135-
drivers/hwmon/lineage-pem.c:136: result = i2c_smbus_read_block_data(client, command, block_buffer);
drivers/hwmon/lineage-pem.c-137- if (unlikely(result < 0))
--
drivers/hwmon/lm93.c=881=static void lm93_read_block(struct i2c_client *client, u8 fbn, u8 *values)
--
drivers/hwmon/lm93.c-885- for (i = 1; i <= MAX_RETRIES; i++) {
drivers/hwmon/lm93.c:886: result = i2c_smbus_read_block_data(client,
drivers/hwmon/lm93.c-887- lm93_block_read_cmds[fbn].cmd, lm93_block_buffer);
--
drivers/hwmon/pmbus/acbel-fsg032.c=18=static ssize_t acbel_fsg032_debugfs_read(struct file *file, char __user *buf, size_t count,
--
drivers/hwmon/pmbus/acbel-fsg032.c-25-
drivers/hwmon/pmbus/acbel-fsg032.c:26: rc = i2c_smbus_read_block_data(client, ACBEL_MFR_FW_REVISION, data);
drivers/hwmon/pmbus/acbel-fsg032.c-27- if (rc < 0)
--
drivers/hwmon/pmbus/acbel-fsg032.c=66=static int acbel_fsg032_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/acbel-fsg032.c-71-
drivers/hwmon/pmbus/acbel-fsg032.c:72: rc = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/acbel-fsg032.c-73- if (rc < 0) {
--
drivers/hwmon/pmbus/acbel-fsg032.c-82-
drivers/hwmon/pmbus/acbel-fsg032.c:83: rc = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/acbel-fsg032.c-84- if (rc < 0) {
--
drivers/hwmon/pmbus/adm1266.c=162=static int adm1266_gpio_get(struct gpio_chip *chip, unsigned int offset)
--
drivers/hwmon/pmbus/adm1266.c-174-
drivers/hwmon/pmbus/adm1266.c:175: ret = i2c_smbus_read_block_data(data->client, pmbus_cmd, read_buf);
drivers/hwmon/pmbus/adm1266.c-176- if (ret < 0)
--
drivers/hwmon/pmbus/adm1266.c=186=static int adm1266_gpio_get_multiple(struct gpio_chip *chip, unsigned long *mask,
--
drivers/hwmon/pmbus/adm1266.c-194-
drivers/hwmon/pmbus/adm1266.c:195: ret = i2c_smbus_read_block_data(data->client, ADM1266_GPIO_STATUS, read_buf);
drivers/hwmon/pmbus/adm1266.c-196- if (ret < 0)
--
drivers/hwmon/pmbus/adm1266.c-206-
drivers/hwmon/pmbus/adm1266.c:207: ret = i2c_smbus_read_block_data(data->client, ADM1266_PDIO_STATUS, read_buf);
drivers/hwmon/pmbus/adm1266.c-208- if (ret < 0)
--
drivers/hwmon/pmbus/adm1266.c=348=static int adm1266_nvmem_read_blackbox(struct adm1266_data *data, u8 *read_buff)
--
drivers/hwmon/pmbus/adm1266.c-354-
drivers/hwmon/pmbus/adm1266.c:355: ret = i2c_smbus_read_block_data(data->client, ADM1266_BLACKBOX_INFO, buf);
drivers/hwmon/pmbus/adm1266.c-356- if (ret < 0)
--
drivers/hwmon/pmbus/adm1275.c=512=static int adm1275_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/adm1275.c-531-
drivers/hwmon/pmbus/adm1275.c:532: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer);
drivers/hwmon/pmbus/adm1275.c-533- if (ret < 0) {
--
drivers/hwmon/pmbus/adm1275.c-542-
drivers/hwmon/pmbus/adm1275.c:543: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
drivers/hwmon/pmbus/adm1275.c-544- if (ret < 0) {
--
drivers/hwmon/pmbus/bpa-rs600.c=156=static int bpa_rs600_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/bpa-rs600.c-168-
drivers/hwmon/pmbus/bpa-rs600.c:169: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/bpa-rs600.c-170- if (ret < 0) {
--
drivers/hwmon/pmbus/crps.c=30=static int crps_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/crps.c-35-
drivers/hwmon/pmbus/crps.c:36: rc = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/crps.c-37- if (rc < 0)
--
drivers/hwmon/pmbus/dps920ab.c=136=static int dps920ab_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/dps920ab.c-145-
drivers/hwmon/pmbus/dps920ab.c:146: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/dps920ab.c-147- if (ret < 0) {
--
drivers/hwmon/pmbus/dps920ab.c-161-
drivers/hwmon/pmbus/dps920ab.c:162: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/dps920ab.c-163- if (ret < 0) {
--
drivers/hwmon/pmbus/fsp-3y.c=203=static int fsp3y_detect(struct i2c_client *client)
--
drivers/hwmon/pmbus/fsp-3y.c-207-
drivers/hwmon/pmbus/fsp-3y.c:208: rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/fsp-3y.c-209- if (rv < 0)
--
drivers/hwmon/pmbus/ibm-cffps.c=482=static int ibm_cffps_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/ibm-cffps.c-509-
drivers/hwmon/pmbus/ibm-cffps.c:510: rc = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, mfg_id);
drivers/hwmon/pmbus/ibm-cffps.c-511- if (rc < 0) {
--
drivers/hwmon/pmbus/inspur-ipsps.c=53=static ssize_t ipsps_string_show(struct device *dev,
--
drivers/hwmon/pmbus/inspur-ipsps.c-64- reg = ipsps_regs[attr->index];
drivers/hwmon/pmbus/inspur-ipsps.c:65: rc = i2c_smbus_read_block_data(client, reg, data);
drivers/hwmon/pmbus/inspur-ipsps.c-66- if (rc < 0)
--
drivers/hwmon/pmbus/inspur-ipsps.c=76=static ssize_t ipsps_fw_version_show(struct device *dev,
--
drivers/hwmon/pmbus/inspur-ipsps.c-86- reg = ipsps_regs[attr->index];
drivers/hwmon/pmbus/inspur-ipsps.c:87: rc = i2c_smbus_read_block_data(client, reg, data);
drivers/hwmon/pmbus/inspur-ipsps.c-88- if (rc < 0)
--
drivers/hwmon/pmbus/ir35221.c=70=static int ir35221_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/ir35221.c-81-
drivers/hwmon/pmbus/ir35221.c:82: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/ir35221.c-83- if (ret < 0) {
--
drivers/hwmon/pmbus/ir35221.c-91-
drivers/hwmon/pmbus/ir35221.c:92: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/ir35221.c-93- if (ret < 0) {
--
drivers/hwmon/pmbus/lt7182s.c=103=static int lt7182s_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/lt7182s.c-115-
drivers/hwmon/pmbus/lt7182s.c:116: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/lt7182s.c-117- if (ret < 0) {
--
drivers/hwmon/pmbus/lt7182s.c-126-
drivers/hwmon/pmbus/lt7182s.c:127: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/lt7182s.c-128- if (ret < 0) {
--
drivers/hwmon/pmbus/ltc2978.c=604=static int ltc2978_get_id(struct i2c_client *client)
--
drivers/hwmon/pmbus/ltc2978.c-617-
drivers/hwmon/pmbus/ltc2978.c:618: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/ltc2978.c-619- if (ret < 0)
--
drivers/hwmon/pmbus/ltc2978.c-623-
drivers/hwmon/pmbus/ltc2978.c:624: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/ltc2978.c-625- if (ret < 0)
--
drivers/hwmon/pmbus/ltc4286.c=67=static int ltc4286_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/ltc4286.c-75-
drivers/hwmon/pmbus/ltc4286.c:76: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer);
drivers/hwmon/pmbus/ltc4286.c-77- if (ret < 0) {
--
drivers/hwmon/pmbus/ltc4286.c-91-
drivers/hwmon/pmbus/ltc4286.c:92: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer);
drivers/hwmon/pmbus/ltc4286.c-93- if (ret < 0) {
--
drivers/hwmon/pmbus/max15301.c=57=static int max15301_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/max15301.c-68-
drivers/hwmon/pmbus/max15301.c:69: status = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, device_id);
drivers/hwmon/pmbus/max15301.c-70- if (status < 0) {
--
drivers/hwmon/pmbus/max16601.c=71=static int max16601_read_word(struct i2c_client *client, int page, int phase,
--
drivers/hwmon/pmbus/max16601.c-90- return ret;
drivers/hwmon/pmbus/max16601.c:91: ret = i2c_smbus_read_block_data(client,
drivers/hwmon/pmbus/max16601.c-92- REG_PHASE_REPORTING,
--
drivers/hwmon/pmbus/max16601.c=274=static int max16601_get_id(struct i2c_client *client)
--
drivers/hwmon/pmbus/max16601.c-280-
drivers/hwmon/pmbus/max16601.c:281: ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
drivers/hwmon/pmbus/max16601.c-282- if (ret < 0 || ret < 11)
--
drivers/hwmon/pmbus/max20730.c=661=static int max20730_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/max20730.c-674-
drivers/hwmon/pmbus/max20730.c:675: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/max20730.c-676- if (ret < 0) {
--
drivers/hwmon/pmbus/max20730.c-695- */
drivers/hwmon/pmbus/max20730.c:696: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/max20730.c-697- if (ret < 0) {
--
drivers/hwmon/pmbus/max20730.c-706-
drivers/hwmon/pmbus/max20730.c:707: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_REVISION, buf);
drivers/hwmon/pmbus/max20730.c-708- if (ret < 0) {
--
drivers/hwmon/pmbus/mp5920.c=39=static int mp5920_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/mp5920.c-48-
drivers/hwmon/pmbus/mp5920.c:49: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/mp5920.c-50- if (ret < 0)
--
drivers/hwmon/pmbus/pim4328.c=133=static int pim4328_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/pim4328.c-152-
drivers/hwmon/pmbus/pim4328.c:153: status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
drivers/hwmon/pmbus/pim4328.c-154- if (status < 0) {
--
drivers/hwmon/pmbus/pmbus_core.c=513=static int pmbus_read_block_data(struct i2c_client *client, int page, u8 reg,
--
drivers/hwmon/pmbus/pmbus_core.c-522- pmbus_wait(client);
drivers/hwmon/pmbus/pmbus_core.c:523: rv = i2c_smbus_read_block_data(client, reg, data_buf);
drivers/hwmon/pmbus/pmbus_core.c-524- pmbus_update_ts(client, 0);
--
drivers/hwmon/pmbus/pxe1610.c=90=static int pxe1610_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/pxe1610.c-109- /* Read Manufacturer id */
drivers/hwmon/pmbus/pxe1610.c:110: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/pxe1610.c-111- if (ret < 0) {
--
drivers/hwmon/pmbus/q54sj108a2.c=74=static ssize_t q54sj108a2_debugfs_read(struct file *file, char __user *buf,
--
drivers/hwmon/pmbus/q54sj108a2.c-121- case Q54SJ108A2_DEBUGFS_MFR_ID:
drivers/hwmon/pmbus/q54sj108a2.c:122: rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_ID, data);
drivers/hwmon/pmbus/q54sj108a2.c-123- if (rc < 0)
--
drivers/hwmon/pmbus/q54sj108a2.c-126- case Q54SJ108A2_DEBUGFS_MFR_MODEL:
drivers/hwmon/pmbus/q54sj108a2.c:127: rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_MODEL, data);
drivers/hwmon/pmbus/q54sj108a2.c-128- if (rc < 0)
--
drivers/hwmon/pmbus/q54sj108a2.c-131- case Q54SJ108A2_DEBUGFS_MFR_REVISION:
drivers/hwmon/pmbus/q54sj108a2.c:132: rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_REVISION, data);
drivers/hwmon/pmbus/q54sj108a2.c-133- if (rc < 0)
--
drivers/hwmon/pmbus/q54sj108a2.c-136- case Q54SJ108A2_DEBUGFS_MFR_LOCATION:
drivers/hwmon/pmbus/q54sj108a2.c:137: rc = i2c_smbus_read_block_data(psu->client, PMBUS_MFR_LOCATION, data);
drivers/hwmon/pmbus/q54sj108a2.c-138- if (rc < 0)
--
drivers/hwmon/pmbus/q54sj108a2.c-148- case Q54SJ108A2_DEBUGFS_BLACKBOX_READ:
drivers/hwmon/pmbus/q54sj108a2.c:149: rc = i2c_smbus_read_block_data(psu->client, READ_HISTORY_EVENTS, data);
drivers/hwmon/pmbus/q54sj108a2.c-150- if (rc < 0)
--
drivers/hwmon/pmbus/q54sj108a2.c-157- case Q54SJ108A2_DEBUGFS_FLASH_KEY:
drivers/hwmon/pmbus/q54sj108a2.c:158: rc = i2c_smbus_read_block_data(psu->client, PMBUS_FLASH_KEY_WRITE, data);
drivers/hwmon/pmbus/q54sj108a2.c-159- if (rc < 0)
--
drivers/hwmon/pmbus/q54sj108a2.c=278=static int q54sj108a2_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/q54sj108a2.c-298-
drivers/hwmon/pmbus/q54sj108a2.c:299: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
drivers/hwmon/pmbus/q54sj108a2.c-300- if (ret < 0) {
--
drivers/hwmon/pmbus/q54sj108a2.c-312- */
drivers/hwmon/pmbus/q54sj108a2.c:313: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
drivers/hwmon/pmbus/q54sj108a2.c-314- if (ret < 0) {
--
drivers/hwmon/pmbus/q54sj108a2.c-323-
drivers/hwmon/pmbus/q54sj108a2.c:324: ret = i2c_smbus_read_block_data(client, PMBUS_MFR_REVISION, buf);
drivers/hwmon/pmbus/q54sj108a2.c-325- if (ret < 0) {
--
drivers/hwmon/pmbus/stpddc60.c=196=static int stpddc60_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/stpddc60.c-207-
drivers/hwmon/pmbus/stpddc60.c:208: status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
drivers/hwmon/pmbus/stpddc60.c-209- if (status < 0) {
--
drivers/hwmon/pmbus/tps53679.c=89=static int tps53679_identify_chip(struct i2c_client *client,
--
drivers/hwmon/pmbus/tps53679.c-104-
drivers/hwmon/pmbus/tps53679.c:105: ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
drivers/hwmon/pmbus/tps53679.c-106- if (ret < 0)
--
drivers/hwmon/pmbus/tps53679.c=168=static int tps53676_identify(struct i2c_client *client,
--
drivers/hwmon/pmbus/tps53679.c-174-
drivers/hwmon/pmbus/tps53679.c:175: ret = i2c_smbus_read_block_data(client, PMBUS_IC_DEVICE_ID, buf);
drivers/hwmon/pmbus/tps53679.c-176- if (ret < 0)
--
drivers/hwmon/pmbus/tps53679.c-182-
drivers/hwmon/pmbus/tps53679.c:183: ret = i2c_smbus_read_block_data(client, TPS53676_USER_DATA_03, buf);
drivers/hwmon/pmbus/tps53679.c-184- if (ret < 0)
--
drivers/hwmon/pmbus/ucd9000.c=386=static int ucd9000_get_mfr_status(struct i2c_client *client, u8 *buffer)
--
drivers/hwmon/pmbus/ucd9000.c-392-
drivers/hwmon/pmbus/ucd9000.c:393: return i2c_smbus_read_block_data(client, UCD9000_MFR_STATUS, buffer);
drivers/hwmon/pmbus/ucd9000.c-394-}
--
drivers/hwmon/pmbus/ucd9000.c=503=static int ucd9000_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/ucd9000.c-516-
drivers/hwmon/pmbus/ucd9000.c:517: ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID,
drivers/hwmon/pmbus/ucd9000.c-518- block_buffer);
--
drivers/hwmon/pmbus/ucd9000.c-566- /* Everything else is configurable */
drivers/hwmon/pmbus/ucd9000.c:567: ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG,
drivers/hwmon/pmbus/ucd9000.c-568- block_buffer);
--
drivers/hwmon/pmbus/ucd9000.c-602- UCD9000_FAN_CONFIG_INDEX, i);
drivers/hwmon/pmbus/ucd9000.c:603: ret = i2c_smbus_read_block_data(client,
drivers/hwmon/pmbus/ucd9000.c-604- UCD9000_FAN_CONFIG,
--
drivers/hwmon/pmbus/ucd9200.c=74=static int ucd9200_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/ucd9200.c-86-
drivers/hwmon/pmbus/ucd9200.c:87: ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID,
drivers/hwmon/pmbus/ucd9200.c-88- block_buffer);
--
drivers/hwmon/pmbus/ucd9200.c-119-
drivers/hwmon/pmbus/ucd9200.c:120: ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO,
drivers/hwmon/pmbus/ucd9200.c-121- block_buffer);
--
drivers/hwmon/pmbus/zl6100.c=279=static int zl6100_probe(struct i2c_client *client)
--
drivers/hwmon/pmbus/zl6100.c-291-
drivers/hwmon/pmbus/zl6100.c:292: ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID,
drivers/hwmon/pmbus/zl6100.c-293- device_id);
--
drivers/hwmon/pt5161l.c=117=static int pt5161l_read_block_data(struct pt5161l_data *data, u32 address,
--
drivers/hwmon/pt5161l.c-147-
drivers/hwmon/pt5161l.c:148: ret = i2c_smbus_read_block_data(client, (cmd | 0x1),
drivers/hwmon/pt5161l.c-149- rbuf);
]
|
| 50/2 |
2026/05/30 15:15 |
llm |
expert |
0m
Model:
Tokens:
input: 72192
output: 45
thoughts: 249
Thoughts: **Investigating HWmon Driver I2C SMBus Probe/Detect Usage**
Okay, so I'm seeing a lot of hwmon drivers making heavy use of `i2c_smbus_read_block_data` within their `probe` or `detect` functions, which makes sense. I mean, they're likely trying to identify and initialize hardware, right? But wait, I need to keep this straight in my head: `probe` is triggered only when the device is explicitly instantiated, but `detect` is the one called automatically during adapter registration. I really need to double check how many of these drivers actually *use* the `detect` function, since that's where the automatic device identification happens.
Alright, let's take a look. Aha, here's `fsp3y_detect` in `drivers/hwmon/pmbus/fsp-3y.c`: It's attempting an `i2c_smbus_read_block_data` call. Let me quote it:
```c
static int fsp3y_detect(struct i2c_client *client)
{
int rv;
u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
if (rv < 0)
return rv;
...
```
Right, of course. `fsp3y_detect` is itself *called* by `i2c_detect`, but only if the driver defines both a `detect` function *and* an `address_list`. Time to check out the rest of `fsp-3y.c` and make sure I get the full context.
|
| 51/2 |
2026/05/30 15:15 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/hwmon/pmbus/fsp-3y.c Name:fsp3y_driver]
Results: map[SourceCode: SourceFile:]
|
| 52/2 |
2026/05/30 15:15 |
llm |
expert |
0m
Model:
Tokens:
input: 72894
output: 45
thoughts: 28
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Diving into the FSP3Y Driver**
Right, let's get down to brass tacks. I need to take a look at the `fsp3y` driver. The obvious place to start is `drivers/hwmon/pmbus/fsp-3y.c`. It's almost certain that's where the core logic for that specific power supply model will reside. This is the starting point for any debugging, feature implementation, or simply understanding how the system interacts with the hardware. I'll need to remember to check for any vendor-specific extensions or quirks. Hopefully the code is well-commented. Let's see what we can find.
|
| 53/2 |
2026/05/30 15:15 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/hwmon/pmbus/fsp-3y.c Name:fsp3y_detect]
Results: map[SourceCode: SourceFile:]
|
| 54/2 |
2026/05/30 15:15 |
llm |
expert |
0m
Model:
Tokens:
input: 73152
output: 43
thoughts: 25
Thoughts: Here's my take on the thought process:
**Diving into the Code: Initial Observation**
Okay, I'm grepping through the output, scanning for anything that jumps out. And there it is! I see `fsp3y_detect`. That name rings a bell... I've got a feeling this is relevant. Alright, time to dig into the source code for that file. Let's get a look at what this function is actually doing. This feels like the key, so let's see what's what.
|
| 55/2 |
2026/05/30 15:15 |
tool |
read-file |
0m
Args: map[File:drivers/hwmon/pmbus/fsp-3y.c FirstLine:190 LineCount:50]
Results: map[Contents: 190: PMBUS_HAVE_POUT |
191: PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3,
192: .func[YH5151E_PAGE_5V_LOG] =
193: PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
194: PMBUS_HAVE_POUT,
195: .func[YH5151E_PAGE_3V3_LOG] =
196: PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
197: PMBUS_HAVE_POUT,
198: .read_word_data = fsp3y_read_word_data,
199: .read_byte_data = fsp3y_read_byte_data,
200: }
201: };
202:
203: static int fsp3y_detect(struct i2c_client *client)
204: {
205: int rv;
206: u8 buf[I2C_SMBUS_BLOCK_MAX + 1];
207:
208: rv = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, buf);
209: if (rv < 0)
210: return rv;
211:
212: buf[rv] = '\0';
213:
214: if (rv == 8) {
215: if (!strcmp(buf, "YM-2151E"))
216: return ym2151e;
217: else if (!strcmp(buf, "YH-5151E"))
218: return yh5151e;
219: }
220:
221: dev_err(&client->dev, "Unsupported model %.*s\n", rv, buf);
222: return -ENODEV;
223: }
224:
225: static const struct i2c_device_id fsp3y_id[] = {
226: {"ym2151e", ym2151e},
227: {"yh5151e", yh5151e},
228: { }
229: };
230:
231: static int fsp3y_probe(struct i2c_client *client)
232: {
233: struct fsp3y_data *data;
234: const struct i2c_device_id *id;
235: int rv;
236:
237: data = devm_kzalloc(&client->dev, sizeof(struct fsp3y_data), GFP_KERNEL);
238: if (!data)
239: return -ENOMEM;
]
|
| 56/2 |
2026/05/30 15:15 |
llm |
expert |
0m
Model:
Tokens:
input: 74164
output: 43
thoughts: 28
Thoughts: Okay, let's see...
**Analyzing the Call Stack**
Wait a minute, so `fsp3y_detect` is *actually* called from within `fsp3y_probe`? That's not immediately obvious from the function names alone. I need to dig deeper into the code to understand the context of that relationship. Knowing that direct call path is essential; it's going to influence how I analyze the behavior. Now, let's see what else the code reveals.
|
| 57/2 |
2026/05/30 15:15 |
tool |
read-file |
0m
Args: map[File:drivers/hwmon/pmbus/fsp-3y.c FirstLine:240 LineCount:50]
Results: map[Contents: 240:
241: data->chip = fsp3y_detect(client);
242: if (data->chip < 0)
243: return data->chip;
244:
245: id = i2c_match_id(fsp3y_id, client);
246: if (data->chip != id->driver_data)
247: dev_warn(&client->dev, "Device mismatch: Configured %s (%d), detected %d\n",
248: id->name, (int)id->driver_data, data->chip);
249:
250: rv = i2c_smbus_read_byte_data(client, PMBUS_PAGE);
251: if (rv < 0)
252: return rv;
253: data->page = rv;
254:
255: data->info = fsp3y_info[data->chip];
256:
257: /*
258: * YH-5151E sometimes reports vout in linear11 and sometimes in
259: * linear16. This depends on the exact individual piece of hardware. One
260: * YH-5151E can use linear16 and another might use linear11 instead.
261: *
262: * The format can be recognized by reading VOUT_MODE - if it doesn't
263: * report a valid exponent, then vout uses linear11. Otherwise, the
264: * device is compliant and uses linear16.
265: */
266: data->vout_linear_11 = false;
267: if (data->chip == yh5151e) {
268: rv = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
269: if (rv < 0)
270: return rv;
271:
272: if (rv == 0xFF)
273: data->vout_linear_11 = true;
274: }
275:
276: return pmbus_do_probe(client, &data->info);
277: }
278:
279: MODULE_DEVICE_TABLE(i2c, fsp3y_id);
280:
281: static struct i2c_driver fsp3y_driver = {
282: .driver = {
283: .name = "fsp3y",
284: },
285: .probe = fsp3y_probe,
286: .id_table = fsp3y_id
287: };
288:
289: module_i2c_driver(fsp3y_driver);
]
|
| 58/2 |
2026/05/30 15:15 |
llm |
expert |
0m
Model:
Tokens:
input: 75145
output: 34
thoughts: 121
Thoughts: **Analyzing the `fsp3y` Driver Initialization**
Okay, so it looks like `fsp3y_detect` is being called from `fsp3y_probe`. That makes sense. Now, `fsp3y_probe` only gets triggered during the actual device instantiation, right? Things like a sysfs `new_device` event, or through Device Tree (DT) or ACPI. It's not happening automatically when the adapter itself registers. That's good to keep in mind, because the `fsp3y_driver` doesn't seem to define a `.detect` function, nor does it define an `.address_list`. This helps narrow down where and how this driver is being loaded. I need to make sure I understand the full call structure.
Let's do a quick scan to make sure, no other driver is using `.detect` and using `i2c_smbus_read_block_data`.
|
| 59/2 |
2026/05/30 15:15 |
tool |
grepper |
0m
Args: map[Expression:\.detect\s*=\s*.* PathPrefix:drivers/hwmon/]
Results: map[Output:drivers/hwmon/adc128d818.c=494=static struct i2c_driver adc128_driver = {
--
drivers/hwmon/adc128d818.c-501- .id_table = adc128_id,
drivers/hwmon/adc128d818.c:502: .detect = adc128_detect,
drivers/hwmon/adc128d818.c-503- .address_list = normal_i2c,
--
drivers/hwmon/adm1025.c=557=static struct i2c_driver adm1025_driver = {
--
drivers/hwmon/adm1025.c-563- .id_table = adm1025_id,
drivers/hwmon/adm1025.c:564: .detect = adm1025_detect,
drivers/hwmon/adm1025.c-565- .address_list = normal_i2c,
--
drivers/hwmon/adm1026.c=1865=static struct i2c_driver adm1026_driver = {
--
drivers/hwmon/adm1026.c-1871- .id_table = adm1026_id,
drivers/hwmon/adm1026.c:1872: .detect = adm1026_detect,
drivers/hwmon/adm1026.c-1873- .address_list = normal_i2c,
--
drivers/hwmon/adm1029.c=390=static struct i2c_driver adm1029_driver = {
--
drivers/hwmon/adm1029.c-396- .id_table = adm1029_id,
drivers/hwmon/adm1029.c:397: .detect = adm1029_detect,
drivers/hwmon/adm1029.c-398- .address_list = normal_i2c,
--
drivers/hwmon/adm1031.c=1064=static struct i2c_driver adm1031_driver = {
--
drivers/hwmon/adm1031.c-1070- .id_table = adm1031_id,
drivers/hwmon/adm1031.c:1071: .detect = adm1031_detect,
drivers/hwmon/adm1031.c-1072- .address_list = normal_i2c,
--
drivers/hwmon/adm9240.c=804=static struct i2c_driver adm9240_driver = {
--
drivers/hwmon/adm9240.c-810- .id_table = adm9240_id,
drivers/hwmon/adm9240.c:811: .detect = adm9240_detect,
drivers/hwmon/adm9240.c-812- .address_list = normal_i2c,
--
drivers/hwmon/adt7411.c=678=static struct i2c_driver adt7411_driver = {
--
drivers/hwmon/adt7411.c-683- .id_table = adt7411_id,
drivers/hwmon/adt7411.c:684: .detect = adt7411_detect,
drivers/hwmon/adt7411.c-685- .address_list = normal_i2c,
--
drivers/hwmon/adt7462.c=1817=static struct i2c_driver adt7462_driver = {
--
drivers/hwmon/adt7462.c-1823- .id_table = adt7462_id,
drivers/hwmon/adt7462.c:1824: .detect = adt7462_detect,
drivers/hwmon/adt7462.c-1825- .address_list = normal_i2c,
--
drivers/hwmon/adt7470.c=1304=static struct i2c_driver adt7470_driver = {
--
drivers/hwmon/adt7470.c-1311- .id_table = adt7470_id,
drivers/hwmon/adt7470.c:1312: .detect = adt7470_detect,
drivers/hwmon/adt7470.c-1313- .address_list = normal_i2c,
--
drivers/hwmon/adt7475.c=1994=static struct i2c_driver adt7475_driver = {
--
drivers/hwmon/adt7475.c-2001- .id_table = adt7475_id,
drivers/hwmon/adt7475.c:2002: .detect = adt7475_detect,
drivers/hwmon/adt7475.c-2003- .address_list = normal_i2c,
--
drivers/hwmon/amc6821.c=1104=static struct i2c_driver amc6821_driver = {
--
drivers/hwmon/amc6821.c-1111- .id_table = amc6821_id,
drivers/hwmon/amc6821.c:1112: .detect = amc6821_detect,
drivers/hwmon/amc6821.c-1113- .address_list = normal_i2c,
--
drivers/hwmon/asb100.c=221=static struct i2c_driver asb100_driver = {
--
drivers/hwmon/asb100.c-228- .id_table = asb100_id,
drivers/hwmon/asb100.c:229: .detect = asb100_detect,
drivers/hwmon/asb100.c-230- .address_list = normal_i2c,
--
drivers/hwmon/asc7621.c=1189=static struct i2c_driver asc7621_driver = {
--
drivers/hwmon/asc7621.c-1196- .id_table = asc7621_id,
drivers/hwmon/asc7621.c:1197: .detect = asc7621_detect,
drivers/hwmon/asc7621.c-1198- .address_list = normal_i2c,
--
drivers/hwmon/dme1737.c=2524=static struct i2c_driver dme1737_i2c_driver = {
--
drivers/hwmon/dme1737.c-2531- .id_table = dme1737_id,
drivers/hwmon/dme1737.c:2532: .detect = dme1737_i2c_detect,
drivers/hwmon/dme1737.c-2533- .address_list = normal_i2c,
--
drivers/hwmon/emc1403.c=685=static struct i2c_driver sensor_emc1403 = {
--
drivers/hwmon/emc1403.c-689- },
drivers/hwmon/emc1403.c:690: .detect = emc1403_detect,
drivers/hwmon/emc1403.c-691- .probe = emc1403_probe,
--
drivers/hwmon/emc2103.c=655=static struct i2c_driver emc2103_driver = {
--
drivers/hwmon/emc2103.c-661- .id_table = emc2103_ids,
drivers/hwmon/emc2103.c:662: .detect = emc2103_detect,
drivers/hwmon/emc2103.c-663- .address_list = normal_i2c,
--
drivers/hwmon/emc6w201.c=472=static struct i2c_driver emc6w201_driver = {
--
drivers/hwmon/emc6w201.c-478- .id_table = emc6w201_id,
drivers/hwmon/emc6w201.c:479: .detect = emc6w201_detect,
drivers/hwmon/emc6w201.c-480- .address_list = normal_i2c,
--
drivers/hwmon/f75375s.c=887=static struct i2c_driver f75375_driver = {
--
drivers/hwmon/f75375s.c-894- .id_table = f75375_id,
drivers/hwmon/f75375s.c:895: .detect = f75375_detect,
drivers/hwmon/f75375s.c-896- .address_list = normal_i2c,
--
drivers/hwmon/fschmd.c=239=static struct i2c_driver fschmd_driver = {
--
drivers/hwmon/fschmd.c-246- .id_table = fschmd_id,
drivers/hwmon/fschmd.c:247: .detect = fschmd_detect,
drivers/hwmon/fschmd.c-248- .address_list = normal_i2c,
--
drivers/hwmon/ftsteutates.c=644=static struct i2c_driver fts_driver = {
--
drivers/hwmon/ftsteutates.c-650- .probe = fts_probe,
drivers/hwmon/ftsteutates.c:651: .detect = fts_detect,
drivers/hwmon/ftsteutates.c-652- .address_list = normal_i2c,
--
drivers/hwmon/gl518sm.c=650=static struct i2c_driver gl518_driver = {
--
drivers/hwmon/gl518sm.c-656- .id_table = gl518_id,
drivers/hwmon/gl518sm.c:657: .detect = gl518_detect,
drivers/hwmon/gl518sm.c-658- .address_list = normal_i2c,
--
drivers/hwmon/gl520sm.c=893=static struct i2c_driver gl520_driver = {
--
drivers/hwmon/gl520sm.c-899- .id_table = gl520_id,
drivers/hwmon/gl520sm.c:900: .detect = gl520_detect,
drivers/hwmon/gl520sm.c-901- .address_list = normal_i2c,
--
drivers/hwmon/jc42.c=593=static struct i2c_driver jc42_driver = {
--
drivers/hwmon/jc42.c-602- .id_table = jc42_id,
drivers/hwmon/jc42.c:603: .detect = jc42_detect,
drivers/hwmon/jc42.c-604- .address_list = normal_i2c,
--
drivers/hwmon/lm63.c=1158=static struct i2c_driver lm63_driver = {
--
drivers/hwmon/lm63.c-1165- .id_table = lm63_id,
drivers/hwmon/lm63.c:1166: .detect = lm63_detect,
drivers/hwmon/lm63.c-1167- .address_list = normal_i2c,
--
drivers/hwmon/lm73.c=274=static struct i2c_driver lm73_driver = {
--
drivers/hwmon/lm73.c-281- .id_table = lm73_ids,
drivers/hwmon/lm73.c:282: .detect = lm73_detect,
drivers/hwmon/lm73.c-283- .address_list = normal_i2c,
--
drivers/hwmon/lm75.c=1116=static struct i2c_driver lm75_i2c_driver = {
--
drivers/hwmon/lm75.c-1124- .id_table = lm75_i2c_ids,
drivers/hwmon/lm75.c:1125: .detect = lm75_detect,
drivers/hwmon/lm75.c-1126- .address_list = normal_i2c,
--
drivers/hwmon/lm77.c=346=static struct i2c_driver lm77_driver = {
--
drivers/hwmon/lm77.c-352- .id_table = lm77_id,
drivers/hwmon/lm77.c:353: .detect = lm77_detect,
drivers/hwmon/lm77.c-354- .address_list = normal_i2c,
--
drivers/hwmon/lm78.c=658=static struct i2c_driver lm78_driver = {
--
drivers/hwmon/lm78.c-664- .id_table = lm78_i2c_id,
drivers/hwmon/lm78.c:665: .detect = lm78_i2c_detect,
drivers/hwmon/lm78.c-666- .address_list = normal_i2c,
--
drivers/hwmon/lm80.c=631=static struct i2c_driver lm80_driver = {
--
drivers/hwmon/lm80.c-637- .id_table = lm80_id,
drivers/hwmon/lm80.c:638: .detect = lm80_detect,
drivers/hwmon/lm80.c-639- .address_list = normal_i2c,
--
drivers/hwmon/lm83.c=452=static struct i2c_driver lm83_driver = {
--
drivers/hwmon/lm83.c-458- .id_table = lm83_id,
drivers/hwmon/lm83.c:459: .detect = lm83_detect,
drivers/hwmon/lm83.c-460- .address_list = normal_i2c,
--
drivers/hwmon/lm85.c=1690=static struct i2c_driver lm85_driver = {
--
drivers/hwmon/lm85.c-1697- .id_table = lm85_id,
drivers/hwmon/lm85.c:1698: .detect = lm85_detect,
drivers/hwmon/lm85.c-1699- .address_list = normal_i2c,
--
drivers/hwmon/lm87.c=997=static struct i2c_driver lm87_driver = {
--
drivers/hwmon/lm87.c-1004- .id_table = lm87_id,
drivers/hwmon/lm87.c:1005: .detect = lm87_detect,
drivers/hwmon/lm87.c-1006- .address_list = normal_i2c,
--
drivers/hwmon/lm90.c=2971=static struct i2c_driver lm90_driver = {
--
drivers/hwmon/lm90.c-2980- .id_table = lm90_id,
drivers/hwmon/lm90.c:2981: .detect = lm90_detect,
drivers/hwmon/lm90.c-2982- .address_list = normal_i2c,
--
drivers/hwmon/lm92.c=416=static struct i2c_driver lm92_driver = {
--
drivers/hwmon/lm92.c-422- .id_table = lm92_id,
drivers/hwmon/lm92.c:423: .detect = lm92_detect,
drivers/hwmon/lm92.c-424- .address_list = normal_i2c,
--
drivers/hwmon/lm93.c=2633=static struct i2c_driver lm93_driver = {
--
drivers/hwmon/lm93.c-2639- .id_table = lm93_id,
drivers/hwmon/lm93.c:2640: .detect = lm93_detect,
drivers/hwmon/lm93.c-2641- .address_list = normal_i2c,
--
drivers/hwmon/lm95234.c=541=static struct i2c_driver lm95234_driver = {
--
drivers/hwmon/lm95234.c-547- .id_table = lm95234_id,
drivers/hwmon/lm95234.c:548: .detect = lm95234_detect,
drivers/hwmon/lm95234.c-549- .address_list = normal_i2c,
--
drivers/hwmon/lm95241.c=450=static struct i2c_driver lm95241_driver = {
--
drivers/hwmon/lm95241.c-456- .id_table = lm95241_id,
drivers/hwmon/lm95241.c:457: .detect = lm95241_detect,
drivers/hwmon/lm95241.c-458- .address_list = normal_i2c,
--
drivers/hwmon/lm95245.c=562=static struct i2c_driver lm95245_driver = {
--
drivers/hwmon/lm95245.c-569- .id_table = lm95245_id,
drivers/hwmon/lm95245.c:570: .detect = lm95245_detect,
drivers/hwmon/lm95245.c-571- .address_list = normal_i2c,
--
drivers/hwmon/max1619.c=383=static struct i2c_driver max1619_driver = {
--
drivers/hwmon/max1619.c-390- .id_table = max1619_id,
drivers/hwmon/max1619.c:391: .detect = max1619_detect,
drivers/hwmon/max1619.c-392- .address_list = normal_i2c,
--
drivers/hwmon/max1668.c=304=static struct i2c_driver max1668_driver = {
--
drivers/hwmon/max1668.c-310- .id_table = max1668_id,
drivers/hwmon/max1668.c:311: .detect = max1668_detect,
drivers/hwmon/max1668.c-312- .address_list = max1668_addr_list,
--
drivers/hwmon/max31730.c=422=static struct i2c_driver max31730_driver = {
--
drivers/hwmon/max31730.c-430- .id_table = max31730_ids,
drivers/hwmon/max31730.c:431: .detect = max31730_detect,
drivers/hwmon/max31730.c-432- .address_list = normal_i2c,
--
drivers/hwmon/max6639.c=794=static struct i2c_driver max6639_driver = {
--
drivers/hwmon/max6639.c-802- .id_table = max6639_id,
drivers/hwmon/max6639.c:803: .detect = max6639_detect,
drivers/hwmon/max6639.c-804- .address_list = normal_i2c,
--
drivers/hwmon/nct7802.c=1201=static struct i2c_driver nct7802_driver = {
--
drivers/hwmon/nct7802.c-1205- },
drivers/hwmon/nct7802.c:1206: .detect = nct7802_detect,
drivers/hwmon/nct7802.c-1207- .probe = nct7802_probe,
--
drivers/hwmon/nct7904.c=1154=static struct i2c_driver nct7904_driver = {
--
drivers/hwmon/nct7904.c-1160- .id_table = nct7904_id,
drivers/hwmon/nct7904.c:1161: .detect = nct7904_detect,
drivers/hwmon/nct7904.c-1162- .address_list = normal_i2c,
--
drivers/hwmon/smsc47m192.c=626=static struct i2c_driver smsc47m192_driver = {
--
drivers/hwmon/smsc47m192.c-632- .id_table = smsc47m192_id,
drivers/hwmon/smsc47m192.c:633: .detect = smsc47m192_detect,
drivers/hwmon/smsc47m192.c-634- .address_list = normal_i2c,
--
drivers/hwmon/spd5118.c=760=static struct i2c_driver spd5118_i2c_driver = {
--
drivers/hwmon/spd5118.c-768- .id_table = spd5118_i2c_id,
drivers/hwmon/spd5118.c:769: .detect = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? spd5118_detect : NULL,
drivers/hwmon/spd5118.c-770- .address_list = IS_ENABLED(CONFIG_SENSORS_SPD5118_DETECT) ? normal_i2c : NULL,
--
drivers/hwmon/stts751.c=817=static struct i2c_driver stts751_driver = {
--
drivers/hwmon/stts751.c-824- .id_table = stts751_id,
drivers/hwmon/stts751.c:825: .detect = stts751_detect,
drivers/hwmon/stts751.c-826- .alert = stts751_alert,
--
drivers/hwmon/thmc50.c=416=static struct i2c_driver thmc50_driver = {
--
drivers/hwmon/thmc50.c-422- .id_table = thmc50_id,
drivers/hwmon/thmc50.c:423: .detect = thmc50_detect,
drivers/hwmon/thmc50.c-424- .address_list = normal_i2c,
--
drivers/hwmon/tmp401.c=748=static struct i2c_driver tmp401_driver = {
--
drivers/hwmon/tmp401.c-755- .id_table = tmp401_id,
drivers/hwmon/tmp401.c:756: .detect = tmp401_detect,
drivers/hwmon/tmp401.c-757- .address_list = normal_i2c,
--
drivers/hwmon/tmp421.c=467=static struct i2c_driver tmp421_driver = {
--
drivers/hwmon/tmp421.c-474- .id_table = tmp421_id,
drivers/hwmon/tmp421.c:475: .detect = tmp421_detect,
drivers/hwmon/tmp421.c-476- .address_list = normal_i2c,
--
drivers/hwmon/tmp464.c=676=static struct i2c_driver tmp464_driver = {
--
drivers/hwmon/tmp464.c-683- .id_table = tmp464_id,
drivers/hwmon/tmp464.c:684: .detect = tmp464_detect,
drivers/hwmon/tmp464.c-685- .address_list = normal_i2c,
--
drivers/hwmon/w83781d.c=1580=static struct i2c_driver w83781d_driver = {
--
drivers/hwmon/w83781d.c-1588- .id_table = w83781d_ids,
drivers/hwmon/w83781d.c:1589: .detect = w83781d_detect,
drivers/hwmon/w83781d.c-1590- .address_list = normal_i2c,
--
drivers/hwmon/w83791d.c=341=static struct i2c_driver w83791d_driver = {
--
drivers/hwmon/w83791d.c-348- .id_table = w83791d_id,
drivers/hwmon/w83791d.c:349: .detect = w83791d_detect,
drivers/hwmon/w83791d.c-350- .address_list = normal_i2c,
--
drivers/hwmon/w83792d.c=304=static struct i2c_driver w83792d_driver = {
--
drivers/hwmon/w83792d.c-311- .id_table = w83792d_id,
drivers/hwmon/w83792d.c:312: .detect = w83792d_detect,
drivers/hwmon/w83792d.c-313- .address_list = normal_i2c,
--
drivers/hwmon/w83793.c=299=static struct i2c_driver w83793_driver = {
--
drivers/hwmon/w83793.c-306- .id_table = w83793_id,
drivers/hwmon/w83793.c:307: .detect = w83793_detect,
drivers/hwmon/w83793.c-308- .address_list = normal_i2c,
--
drivers/hwmon/w83795.c=2252=static struct i2c_driver w83795_driver = {
--
drivers/hwmon/w83795.c-2260- .class = I2C_CLASS_HWMON,
drivers/hwmon/w83795.c:2261: .detect = w83795_detect,
drivers/hwmon/w83795.c-2262- .address_list = normal_i2c,
--
drivers/hwmon/w83l785ts.c=82=static struct i2c_driver w83l785ts_driver = {
--
drivers/hwmon/w83l785ts.c-89- .id_table = w83l785ts_id,
drivers/hwmon/w83l785ts.c:90: .detect = w83l785ts_detect,
drivers/hwmon/w83l785ts.c-91- .address_list = normal_i2c,
--
drivers/hwmon/w83l786ng.c=759=static struct i2c_driver w83l786ng_driver = {
--
drivers/hwmon/w83l786ng.c-765- .id_table = w83l786ng_id,
drivers/hwmon/w83l786ng.c:766: .detect = w83l786ng_detect,
drivers/hwmon/w83l786ng.c-767- .address_list = normal_i2c,
]
|
| 60/2 |
2026/05/30 15:15 |
llm |
expert |
1m
Model:
Tokens:
input: 83301
output: 45
thoughts: 39
Thoughts: Okay, here's my thought process on that:
**Investigating i2c_smbus_read_block_data Usage**
Hmm, I need to figure out which of these detection functions leverage `i2c_smbus_read_block_data`. That's the key question right now. I know the nuances of using that function, the potential pitfalls and the advantages it presents for different kinds of I2C devices.
Let's start by digging into `w83795_detect`. It's as good a starting point as any to see how this approach is being used, or if it is being used, to identify that specific chip. Knowing which devices are using this function will provide insight into other chipsets that may share a similar structure.
|
| 61/2 |
2026/05/30 15:16 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/hwmon/w83795.c Name:w83795_detect]
Results: map[SourceCode: SourceFile:]
|
| 62/2 |
2026/05/30 15:16 |
llm |
expert |
0m
Model:
Tokens:
input: 83570
output: 45
thoughts: 35
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience:
**Investigating the W83795 Driver's I2C SMBus Interaction**
Alright, I need to understand how the W83795 driver, specifically the one in `drivers/hwmon/w83795.c`, is handling its I2C SMBus communication. My initial move is to zero in on the `i2c_smbus_read_block_data` function. That's a good starting point because that's the canonical function for reading a block of data from an SMBus device. I'm hoping to uncover details about data transfer sizes, addressing schemes, potential error handling, and the overall protocol flow related to accessing the sensor's registers. Grepping for that function should immediately surface the relevant code segments and illuminate how the driver actually implements this read operation, revealing its intended usage.
|
| 63/2 |
2026/05/30 15:16 |
tool |
grepper |
0m
Error:
Args: map[Expression:i2c_smbus_read_block_data PathPrefix:drivers/hwmon/w83795.c]
Results:
|
| 64/2 |
2026/05/30 15:16 |
llm |
expert |
0m
Model:
Tokens:
input: 83862
output: 42
thoughts: 10
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to an expert:
**Investigating a Potential Direction**
Alright, so I'm thinking about something. I'm hitting a bit of a snag here and my mind's starting to cast about for potential solutions or avenues to explore. My attention keeps getting drawn to this function, `lm93_detect`. It's popping up in my internal dialogue as a possible point of interest in this investigation. I'm not entirely sure *why* yet, perhaps because of its name or some association I have with its function. I need to take a closer look and understand why itβs coming to mind. Could it be relevant? Is there something in the logic of `lm93_detect` that might provide a clue or resolve the issue I'm facing? I need to analyze its behavior more.
|
| 65/2 |
2026/05/30 15:16 |
tool |
grepper |
0m
Args: map[Expression:i2c_smbus_read_block_data PathPrefix:drivers/hwmon/lm93.c]
Results: map[Output:drivers/hwmon/lm93.c=881=static void lm93_read_block(struct i2c_client *client, u8 fbn, u8 *values)
--
drivers/hwmon/lm93.c-885- for (i = 1; i <= MAX_RETRIES; i++) {
drivers/hwmon/lm93.c:886: result = i2c_smbus_read_block_data(client,
drivers/hwmon/lm93.c-887- lm93_block_read_cmds[fbn].cmd, lm93_block_buffer);
]
|
| 66/2 |
2026/05/30 15:16 |
llm |
expert |
0m
Model:
Tokens:
input: 84270
output: 39
thoughts: 20
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind the user is an expert:
**Investigating the Data Flow in lm93 Driver**
Right, so I'm looking at this `lm93` driver code again. I need to understand the relationship between `lm93_read_block` and `lm93_detect`. Specifically, the question thatβs just popped into my head is: Is `lm93_read_block` actually *called* from within `lm93_detect`? It seems like a reasonable thing to check, but I need to confirm the data flow, and potential errors, and how the program reacts to them. It would make sense if `lm93_detect` probed for the device, and then if found, set up the read calls in the subsequent functions. But I can't assume. I need to track the execution path to be certain. I'm imagining a quick code search will give the answer, either directly, or at least a list of functions that have those calls. Time to dive into the code...
|
| 67/2 |
2026/05/30 15:16 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:drivers/hwmon/lm93.c Name:lm93_detect]
Results: map[SourceCode: SourceFile:]
|
| 68/2 |
2026/05/30 15:16 |
llm |
expert |
0m
Model:
Tokens:
input: 84582
output: 35
thoughts: 24
Thoughts: Here's a summary of my thought process:
**Diving into the lm93 Driver**
Okay, time to get to the heart of the matter. My immediate instinct is to look at the `lm93_detect` function. That's usually the starting point for any device driver analysis, especially when dealing with hardware monitoring, and I'm pretty sure it's located in `drivers/hwmon/lm93.c`. I'm expecting this function to be responsible for the initial probing and identification of the LM93 chip. Understanding the detection routine will give me a solid foundation for how the driver interacts with the hardware and handles potential variations or issues. Let's see what's in there.
|
| 69/2 |
2026/05/30 15:16 |
tool |
grepper |
0m
Args: map[Expression:lm93_detect PathPrefix:drivers/hwmon/lm93.c]
Results: map[Output:drivers/hwmon/lm93.c=2498=static void lm93_init_client(struct i2c_client *client)
--
drivers/hwmon/lm93.c-2543-/* Return 0 if detection is successful, -ENODEV otherwise */
drivers/hwmon/lm93.c:2544:static int lm93_detect(struct i2c_client *client, struct i2c_board_info *info)
drivers/hwmon/lm93.c-2545-{
--
drivers/hwmon/lm93.c=2633=static struct i2c_driver lm93_driver = {
--
drivers/hwmon/lm93.c-2639- .id_table = lm93_id,
drivers/hwmon/lm93.c:2640: .detect = lm93_detect,
drivers/hwmon/lm93.c-2641- .address_list = normal_i2c,
]
|
| 70/2 |
2026/05/30 15:16 |
llm |
expert |
0m
Model:
Tokens:
input: 85060
output: 39
thoughts: 19
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:
**Investigating the lm93 Module Dependency**
I'm digging into this `lm93` module, and the core question I'm trying to nail down right now is the relationship between the `lm93_detect` and `lm93_read_block` functions. Specifically, does `lm93_detect` actually *call* `lm93_read_block`? Or are they independent, potentially used in separate control flow paths?
Knowing this is critical for understanding the module's overall architecture and how data acquisition and device identification are handled. If `detect` relies on `read_block`, then I'll need to analyze `read_block`'s error handling and assumptions about the device state, as any problems in that function will immediately impact the integrity of the detection routine. Conversely, if they're independent, I can focus on isolating the purpose of each function individually. The implications for debugging, potential refactoring, and overall code maintainability are significant depending on the answer to this dependency question. I need to trace the call paths to be certain.
|
| 71/2 |
2026/05/30 15:17 |
tool |
read-file |
0m
Args: map[File:drivers/hwmon/lm93.c FirstLine:2544 LineCount:50]
Results: map[Contents:2544: static int lm93_detect(struct i2c_client *client, struct i2c_board_info *info)
2545: {
2546: struct i2c_adapter *adapter = client->adapter;
2547: int mfr, ver;
2548: const char *name;
2549:
2550: if (!i2c_check_functionality(adapter, LM93_SMBUS_FUNC_MIN))
2551: return -ENODEV;
2552:
2553: /* detection */
2554: mfr = lm93_read_byte(client, LM93_REG_MFR_ID);
2555: if (mfr != 0x01) {
2556: dev_dbg(&adapter->dev,
2557: "detect failed, bad manufacturer id 0x%02x!\n", mfr);
2558: return -ENODEV;
2559: }
2560:
2561: ver = lm93_read_byte(client, LM93_REG_VER);
2562: switch (ver) {
2563: case LM93_MFR_ID:
2564: case LM93_MFR_ID_PROTOTYPE:
2565: name = "lm93";
2566: break;
2567: case LM94_MFR_ID_2:
2568: case LM94_MFR_ID:
2569: case LM94_MFR_ID_PROTOTYPE:
2570: name = "lm94";
2571: break;
2572: default:
2573: dev_dbg(&adapter->dev,
2574: "detect failed, bad version id 0x%02x!\n", ver);
2575: return -ENODEV;
2576: }
2577:
2578: strscpy(info->type, name, I2C_NAME_SIZE);
2579: dev_dbg(&adapter->dev, "loading %s at %d, 0x%02x\n",
2580: client->name, i2c_adapter_id(client->adapter),
2581: client->addr);
2582:
2583: return 0;
2584: }
2585:
2586: static int lm93_probe(struct i2c_client *client)
2587: {
2588: struct device *dev = &client->dev;
2589: struct lm93_data *data;
2590: struct device *hwmon_dev;
2591: int func;
2592: void (*update)(struct lm93_data *, struct i2c_client *);
2593:
]
|
| 72/2 |
2026/05/30 15:17 |
llm |
expert |
1m
Model:
Tokens:
input: 86170
output: 46
thoughts: 33
Thoughts: Okay, let me think this through.
**Analyzing the Codebase for I2C Device Detection**
Right, so I'm looking at this `lm93_detect` function. And it's pretty straightforward, it's just calling `lm93_read_byte`. That's it. Not much to glean from that directly, in terms of broader I2C probe strategies. It's focused on a specific device, not a generic I2C discovery mechanism.
Hmm, but *how* is this device being detected in the first place? If `lm93_detect` is just reading, there must be something else at play, I need to understand the larger context. Let me switch gears, what's calling `lm93_detect`? I need to look at what other I2C probing mechanisms are in play. I should check the `i2c_default_probe` function. That's likely where the general device detection logic lives. Time to dive deeper and see how it works.
|
| 73/2 |
2026/05/30 15:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/i2c/i2c-core-base.c Name:i2c_default_probe]
Results: map[SourceCode:2406: /*
2407: * Legacy default probe function, mostly relevant for SMBus. The default
2408: * probe method is a quick write, but it is known to corrupt the 24RF08
2409: * EEPROMs due to a state machine bug, and could also irreversibly
2410: * write-protect some EEPROMs, so for address ranges 0x30-0x37 and 0x50-0x5f,
2411: * we use a short byte read instead. Also, some bus drivers don't implement
2412: * quick write, so we fallback to a byte read in that case too.
2413: * On x86, there is another special case for FSC hardware monitoring chips,
2414: * which want regular byte reads (address 0x73.) Fortunately, these are the
2415: * only known chips using this I2C address on PC hardware.
2416: * Returns 1 if probe succeeded, 0 if not.
2417: */
2418: static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
2419: {
2420: int err;
2421: union i2c_smbus_data dummy;
2422:
2423: #ifdef CONFIG_X86
2424: if (addr == 0x73 && (adap->class & I2C_CLASS_HWMON)
2425: && i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE_DATA))
2426: err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
2427: I2C_SMBUS_BYTE_DATA, &dummy);
2428: else
2429: #endif
2430: if (!((addr & ~0x07) == 0x30 || (addr & ~0x0f) == 0x50)
2431: && i2c_check_functionality(adap, I2C_FUNC_SMBUS_QUICK))
2432: err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_WRITE, 0,
2433: I2C_SMBUS_QUICK, NULL);
2434: else if (i2c_check_functionality(adap, I2C_FUNC_SMBUS_READ_BYTE))
2435: err = i2c_smbus_xfer(adap, addr, 0, I2C_SMBUS_READ, 0,
2436: I2C_SMBUS_BYTE, &dummy);
2437: else {
2438: dev_warn(&adap->dev, "No suitable probing method supported for address 0x%02X\n",
2439: addr);
2440: err = -EOPNOTSUPP;
2441: }
2442:
2443: return err >= 0;
2444: }
SourceFile:drivers/i2c/i2c-core-base.c]
|
| 74/2 |
2026/05/30 15:18 |
llm |
expert |
0m
Model:
Tokens:
input: 87330
output: 37
thoughts: 545
Thoughts: **Vulnerability Analysis: I2C SMBus and Malicious USB Devices**
Okay, so I'm thinking about this I2C situation. My initial thought was that the automatic probing process, which utilizes `I2C_SMBUS_QUICK`, `I2C_SMBUS_BYTE_DATA`, or `I2C_SMBUS_BYTE`, wouldn't trigger `I2C_SMBUS_BLOCK_DATA` reads. That seemed like a dead end.
Then, I started to consider a potential attack vector using the `i2c-dev` interface. But, the `/dev/i2c-X` devices are locked down to root or the `i2c` group. So, a regular, unprivileged user is blocked from directly manipulating I2C that way. So, that's not it.
But, if someone plugs in a *malicious USB device* that *emulates an FT260*, that changes everything. The kernel handles I2C adapter registration, and then *automatically probes for hwmon devices*. The probing uses the usual `I2C_SMBUS_QUICK` or `I2C_SMBUS_BYTE`. Now, if the malicious device *responds to the probe*, the kernel might instantiate a hwmon device! If it, say, responds to an address like 0x2e, it might trigger the instantiation of a `w83795` or similar hwmon driver. The key here is the `I2C_CLASS_HWMON` class being registered.
If the malicious USB device answers to the "detect" function of a hwmon driver, the kernel *will* instantiate it. That means sysfs files get created. And here's where it gets interesting: the *unprivileged user can read those sysfs files*! Sysfs files for hwmon devices are *usually world-readable* (0444).
This leads me to think, when that unprivileged user reads a sysfs file, the hwmon driver *might* call `i2c_smbus_read_block_data`! Now, I need to check which hwmon driver that can be auto-detected uses `i2c_smbus_read_block_data` in its sysfs read handlers.
Aha! `lm93` has a `detect` function *and* it uses `i2c_smbus_read_block_data` in `lm93_read_block`. So, I really need to check *where* `lm93_read_block` is called. Bingo!
|
| 75/2 |
2026/05/30 15:18 |
tool |
grepper |
0m
Args: map[Expression:lm93_read_block PathPrefix:drivers/hwmon/lm93.c]
Results: map[Output:drivers/hwmon/lm93.c=874=static u8 lm93_block_buffer[I2C_SMBUS_BLOCK_MAX];
--
drivers/hwmon/lm93.c-880- */
drivers/hwmon/lm93.c:881:static void lm93_read_block(struct i2c_client *client, u8 fbn, u8 *values)
drivers/hwmon/lm93.c-882-{
--
drivers/hwmon/lm93.c=999=static void lm93_update_client_full(struct lm93_data *data,
--
drivers/hwmon/lm93.c-1004- /* in1 - in16: values & limits */
drivers/hwmon/lm93.c:1005: lm93_read_block(client, 3, (u8 *)(data->block3));
drivers/hwmon/lm93.c:1006: lm93_read_block(client, 7, (u8 *)(data->block7));
drivers/hwmon/lm93.c-1007-
drivers/hwmon/lm93.c-1008- /* temp1 - temp4: values */
drivers/hwmon/lm93.c:1009: lm93_read_block(client, 2, (u8 *)(data->block2));
drivers/hwmon/lm93.c-1010-
drivers/hwmon/lm93.c-1011- /* prochot1 - prochot2: values */
drivers/hwmon/lm93.c:1012: lm93_read_block(client, 4, (u8 *)(data->block4));
drivers/hwmon/lm93.c-1013-
drivers/hwmon/lm93.c-1014- /* fan1 - fan4: values & limits */
drivers/hwmon/lm93.c:1015: lm93_read_block(client, 5, (u8 *)(data->block5));
drivers/hwmon/lm93.c:1016: lm93_read_block(client, 8, (u8 *)(data->block8));
drivers/hwmon/lm93.c-1017-
drivers/hwmon/lm93.c-1018- /* pmw control registers */
drivers/hwmon/lm93.c:1019: lm93_read_block(client, 9, (u8 *)(data->block9));
drivers/hwmon/lm93.c-1020-
drivers/hwmon/lm93.c-1021- /* alarm values */
drivers/hwmon/lm93.c:1022: lm93_read_block(client, 1, (u8 *)(&data->block1));
drivers/hwmon/lm93.c-1023-
drivers/hwmon/lm93.c-1024- /* auto/pwm registers */
drivers/hwmon/lm93.c:1025: lm93_read_block(client, 10, (u8 *)(&data->block10));
drivers/hwmon/lm93.c-1026-
]
|
| 76/2 |
2026/05/30 15:18 |
llm |
expert |
0m
Model:
Tokens:
input: 89306
output: 42
thoughts: 2674
Thoughts: **Vulnerability Analysis: Stack Buffer Overflow in FT260 I2C Driver**
Okay, here's the breakdown of this issue. It starts with `lm93_update_client_full` being called from `lm93_update_device`, which is itself triggered by sysfs read handlers. The core problem is that an attacker with physical access could exploit this by emulating an FT260 device (a USB-to-I2C bridge).
Here's how it unfolds: the kernel detects the FT260, registers the I2C adapter and calls `lm93_detect`. A malicious USB device responds to the `lm93_detect` with the correct values. The `lm93` hwmon device is then instantiated, and relevant sysfs files are created.
Now, an unprivileged user (or the attacker) reads a sysfs file, triggering `lm93_update_device`. This, in turn, calls `lm93_read_block`, which calls `i2c_smbus_read_block_data`. This is where it gets interesting and where I see the flaw: within `i2c_smbus_read_block_data`, there's an uninitialized `union i2c_smbus_data data` on the stack. If the first byte, `data.block[0]`, happens to be a large number (greater than 60 in this case), `ft260_smbus_xfer` will read up to that number of bytes from the USB device *into* the stack of `i2c_smbus_read_block_data`.
The vulnerable code is in `i2c_smbus_read_block_data`. It's reading the length of the data from an uninitialized stack variable (`data.block[0]`). This leads directly to a stack buffer overflow. So, if `data.block[0]` contains a value exceeding the allocated buffer size (34 bytes), data from the USB device will overwrite parts of the stack.
Crucially, this can be triggered by a *completely unprivileged user* simply by reading a sysfs file. However, this relies on the presence of a malicious USB device. But even a legitimate FT260 and LM93 chip can lead to a crash! When they are plugged in, an uninitialized `data.block[0]` stack variable may contain a large value. This large value is used as the amount of data to read, and this might result in data from the LM93 chip overflowing.
Here's a key observation: `i2c_smbus_read_block_data` allocates the `data` union on its own stack, meaning it will be uninitialized.
This overflow is directly in `i2c_smbus_read_block_data`, so any driver using this function on an FT260 adapter will be vulnerable. This is because the length of the read is derived from an uninitialized stack variable.
Now, to assess the accessibility: if an attacker *doesn't* have an FT260 device, they can't trigger this via `i2c-dev` because it's root-only. However, they can trigger it via sysfs if a vulnerable hwmon device is present.
Essentially, for a standard system without any special peripherals, this is primarily a Peripheral Trigger. The attacker needs to physically plug in a malicious USB device or an FT260 device *and* have it connected to a device that probes for hwmon devices to be instantiated. The reproducer in this case shows that the exploit can be triggered via `syz_usb_connect` which uses `USB_RAW_GADGET`. Since this device is typically root-only, a non-root user can not use this.
The key takeaway is that an unprivileged local user *cannot* trigger this vulnerability on a typical system without an FT260 or a malicious I2C device. However, if an FT260 is present and a hwmon device is instantiated, an unprivileged user *can* trigger the stack overflow by reading sysfs files. Since an FT260 and a device connected to it is not typical, a non-root user will be unable to trigger this.
Therefore, the vulnerability is not "Accessible From Unprivileged Processes" because it requires a specific peripheral. It is a "Peripheral Trigger" that requires physical (or virtual, with USB passthrough) access to a USB device.
|
| 77/2 |
2026/05/30 15:18 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_probe]
Results: map[SourceCode: 960: static int ft260_probe(struct hid_device *hdev, const struct hid_device_id *id)
961: {
962: struct ft260_device *dev;
963: struct ft260_get_chip_version_report version;
964: int ret;
965:
966: if (!hid_is_usb(hdev))
967: return -EINVAL;
968:
969: dev = devm_kzalloc(&hdev->dev, sizeof(*dev), GFP_KERNEL);
970: if (!dev)
971: return -ENOMEM;
972:
973: ret = hid_parse(hdev);
974: if (ret) {
975: hid_err(hdev, "failed to parse HID\n");
976: return ret;
977: }
978:
979: ret = hid_hw_start(hdev, 0);
980: if (ret) {
981: hid_err(hdev, "failed to start HID HW\n");
982: return ret;
983: }
984:
985: ret = hid_hw_open(hdev);
986: if (ret) {
987: hid_err(hdev, "failed to open HID HW\n");
988: goto err_hid_stop;
989: }
990:
991: ret = ft260_hid_feature_report_get(hdev, FT260_CHIP_VERSION,
992: (u8 *)&version, sizeof(version));
993: if (ret < 0) {
994: hid_err(hdev, "failed to retrieve chip version\n");
995: goto err_hid_close;
996: }
997:
998: hid_info(hdev, "chip code: %02x%02x %02x%02x\n",
999: version.chip_code[0], version.chip_code[1],
1000: version.chip_code[2], version.chip_code[3]);
1001:
1002: ret = ft260_is_interface_enabled(hdev);
1003: if (ret <= 0)
1004: goto err_hid_close;
1005:
1006: hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n",
1007: hdev->version >> 8, hdev->version & 0xff, hdev->name,
1008: hdev->phys);
1009:
1010: hid_set_drvdata(hdev, dev);
1011: dev->hdev = hdev;
1012: dev->adap.owner = THIS_MODULE;
1013: dev->adap.class = I2C_CLASS_HWMON;
1014: dev->adap.algo = &ft260_i2c_algo;
1015: dev->adap.quirks = &ft260_i2c_quirks;
1016: dev->adap.dev.parent = &hdev->dev;
1017: snprintf(dev->adap.name, sizeof(dev->adap.name),
1018: "FT260 usb-i2c bridge");
1019:
1020: mutex_init(&dev->lock);
1021: init_completion(&dev->wait);
1022:
1023: ret = ft260_xfer_status(dev, FT260_I2C_STATUS_BUS_BUSY);
1024: if (ret)
1025: ft260_i2c_reset(hdev);
1026:
1027: i2c_set_adapdata(&dev->adap, dev);
1028: ret = i2c_add_adapter(&dev->adap);
1029: if (ret) {
1030: hid_err(hdev, "failed to add i2c adapter\n");
1031: goto err_hid_close;
1032: }
1033:
1034: ret = sysfs_create_group(&hdev->dev.kobj, &ft260_attr_group);
1035: if (ret < 0) {
1036: hid_err(hdev, "failed to create sysfs attrs\n");
1037: goto err_i2c_free;
1038: }
1039:
1040: return 0;
1041:
1042: err_i2c_free:
1043: i2c_del_adapter(&dev->adap);
1044: err_hid_close:
1045: hid_hw_close(hdev);
1046: err_hid_stop:
1047: hid_hw_stop(hdev);
1048: return ret;
1049: }
SourceFile:drivers/hid/hid-ft260.c]
|
| 78/2 |
2026/05/30 15:18 |
llm |
expert |
1m
Model:
Tokens:
input: 94447
output: 44
thoughts: 155
Thoughts: **My Analysis of the HID Hardware Initialization**
Okay, so `hid_hw_start(hdev, 0)`... that's a key function call. A value of `0` for the second argument has some serious implications. It means it's *not* connecting to any of the standard HID interfaces: no `hidraw`, no `hiddev`, and no `hidinput`. I need to remember that. Wait, wasn't `HID_CONNECT_DEFAULT` defined as the combination of `HID_CONNECT_HIDINPUT | HID_CONNECT_HIDRAW | HID_CONNECT_HIDDEV | HID_CONNECT_FF`? Then passing `0` effectively disables all those connections!
This means, specifically, that there will be no `/dev/hidrawX` entry for this device. Userspace can't send raw HID reports directly through a standard interface. That severely limits my options for interacting with this thing. So, if I can't talk to it the usual way, what are the alternatives?
Alright, let's step back and think. It's either going to be through its I2C adapter registration, or maybe something in the sysfs. Probably the latter. I'd better dig into the sysfs attributes and see what information is available there. That's where I need to focus my attention.
|
| 79/2 |
2026/05/30 15:19 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/hid/hid-ft260.c Name:ft260_attr_group]
Results: map[SourceCode: 944: static const struct attribute_group ft260_attr_group = {
945: .attrs = (struct attribute *[]) {
946: &dev_attr_chip_mode.attr,
947: &dev_attr_pwren_status.attr,
948: &dev_attr_suspend_status.attr,
949: &dev_attr_hid_over_i2c_en.attr,
950: &dev_attr_power_saving_en.attr,
951: &dev_attr_i2c_enable.attr,
952: &dev_attr_uart_mode.attr,
953: &dev_attr_clock_ctl.attr,
954: &dev_attr_i2c_reset.attr,
955: &dev_attr_clock.attr,
956: NULL
957: }
958: };
SourceFile:drivers/hid/hid-ft260.c]
|
| 80/2 |
2026/05/30 15:19 |
llm |
expert |
0m
Model:
Tokens:
input: 95229
output: 66
thoughts: 1903
Thoughts: **My Analysis of the FT260 I2C Driver Vulnerability**
Okay, let's break this down systematically. I see that the core issue isn't within the sysfs attributes themselves. They're just for reading/writing feature reports; the vulnerability lies within the interaction with the I2C adapter. The critical point of entry is `/dev/i2c-X`, which requires root privileges, so an unprivileged user can't trigger this directly *unless*...
If a hwmon device is involved, it introduces a different attack surface via hwmon sysfs files. However, that requires the FT260 to be connected to a real hwmon chip, or an attacker to emulate one using a malicious USB device. Plugging in a malicious USB device is definitely a "Peripheral Trigger," which is my first key takeaway.
Can an unprivileged user trigger this *without* needing to use a malicious USB device? Possibly, but it's a super specific scenario: a legitimate FT260, a legitimate hwmon device using `i2c_smbus_read_block_data` (like `lm93`), and a very unfortunate (for the kernel) size of the uninitialized stack variable. This is such a narrow path, "Accessible From Unprivileged Processes" should be FALSE, and "Peripheral Trigger" should be TRUE.
Alright, let's solidify the assessment across each dimension:
* **Exploitable: TRUE.** We have a clear stack buffer overflow (both read and write).
* **Denial of Service: TRUE.** Kernel panic is highly likely due to stack corruption.
* **Accessible From Unprivileged Processes: FALSE.** Needs root or physical access for a malicious USB.
* **Accessible From User Namespaces: FALSE.** `/dev/i2c-X` isn't accessible in user namespaces.
* **VM Guest Trigger: FALSE.** Not VM-related.
* **VM Host Trigger in the Confidential Computing Context: FALSE.** Irrelevant.
* **Ethernet Network Trigger: FALSE.** Not network-related.
* **Other Remote Trigger: FALSE.** Not remote.
* **Peripheral Trigger: TRUE.** Yes, a malicious USB device can trigger it. Either by emulating the FT260 and waiting for root to use `/dev/i2c-X`, or by emulating a hwmon device.
* **Malicious Filesystem Trigger: FALSE.** Not related to filesystem manipulation.
Now, wait... are there other ways to invoke `i2c_smbus_xfer`? Thinking about `i2c-dev` in a user namespace... those `/dev/i2c-X` device nodes aren't automatically mapped. So a process in a user namespace can't access it unless the host explicitly grants it. This confirms **UserNamespace is FALSE.**
Let's double check if `i2c_smbus_read_block_data` is the *only* way to cause this stack overflow. Looking deeper at `i2cdev_ioctl_smbus`: I see that when the size is `I2C_SMBUS_I2C_BLOCK_DATA`, there's a user controlled read *and write* vulnerability.
If `size == I2C_SMBUS_BLOCK_DATA` *and* `read_write == I2C_SMBUS_WRITE`, it copies from user!
So, the user controls `temp.block[0]`. Then `ft260_smbus_xfer` does a stack out-of-bounds READ in `ft260_smbus_write` (it reads from `data->block` which is `temp.block` on the stack).
And this stack *READ* is a potential information leak (out-of-bounds read).
For `I2C_SMBUS_I2C_BLOCK_DATA` the read case, `i2cdev_ioctl_smbus` DOES copy from user! So `temp.block[0]` is user-controlled. Then `ft260_i2c_read` reads `data->block[0]` bytes from the USB device into `data->block + 1`. Since `data->block[0]` can be up to 255, and `temp.block` is only 34 bytes, this is a clear stack buffer overflow *write*! This can be triggered directly via `i2cdev_ioctl_smbus`!
The attacker, with `/dev/i2c-X` access, could trigger a stack buffer overflow and get arbitrary code execution directly, which is *critical*. However, that requires root. But, the USB device emulation path is still valid. The attacker still needs to emulate
|
| 81/2 |
2026/05/30 15:20 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 82/2 |
2026/05/30 15:20 |
llm |
expert |
0m
Model:
Tokens:
input: 98287
output: 939
thoughts: 726
Thoughts: **Vulnerability Deep Dive: FT260 SMBus Driver**
Okay, the results are in, and it's time to summarize the findings for this FT260 SMBus driver issue. My analysis shows a serious flaw in the `ft260_smbus_xfer` function within `drivers/hid/hid-ft260.c`. The root cause is a missing bounds check. Essentially, the code doesn't properly validate the `data->block[0]` value, which represents the length field used in I2C SMBus operations, before utilizing it for both read and write operations. It's a classic case of trusting user-provided input without enough scrutiny.
Let me break down the scenario: when a user issues an `I2C_SMBUS` ioctl command on a `/dev/i2c-X` device, which is managed by the FT260 driver, `i2cdev_ioctl_smbus` allocates a `union i2c_smbus_data temp` on the stack. This allocation is 34 bytes long. When dealing with block data, the user data, including the crucial length field in `temp.block[0]`, is copied into this temporary buffer.
Here's where the problem manifests. If an attacker crafts data where `temp.block[0]` is a value larger than 32 (up to 255), `ft260_smbus_xfer` will then use this oversized value. This triggers two critical issues:
1. **Out-of-Bounds Read (Stack Leak):** During `I2C_SMBUS_BLOCK_DATA` write operations, the code attempts to read `data->block[0] + 1` bytes from `temp.block` and sends them to the USB device. Since `data->block[0]` can be maliciously large, this leads to an out-of-bounds read from the stack, leaking sensitive kernel stack memory to the USB device. This might reveal stack canaries, pointers, and other valuable information.
2. **Stack Buffer Overflow (Arbitrary Code Execution):** In the case of an `I2C_SMBUS_I2C_BLOCK_DATA` read, the driver reads `data->block[0]` bytes from the USB device and writes them into `temp.block + 1`. A large value for `data->block[0]` causes a stack buffer overflow. This means it overwrites data beyond the allocated buffer, potentially clobbering the return address on the stack. This is a critical vulnerability because it allows an attacker to control the instruction pointer, leading to arbitrary code execution.
Adding to the complexity, I've discovered a related issue when kernel drivers, like hwmon drivers, use `i2c_smbus_read_block_data`. These drivers allocate an uninitialized `union i2c_smbus_data` on the stack. `ft260_smbus_xfer` then incorrectly uses this uninitialized `data->block[0]` as the read length. If the uninitialized stack memory happens to contain a large value (which is quite likely), it also leads to a stack buffer overflow.
Therefore, this vulnerability is definitely exploitable. The stack buffer overflow could allow an attacker to gain control by overwriting the return address. The out-of-bounds read can be used to disclose kernel memory. It also presents a denial-of-service risk, since stack corruption will usually trigger a kernel panic. Access to trigger this vulnerability isn't trivial. The `/dev/i2c-X` nodes are typically restricted to root users. Triggering the bug via hwmon sysfs files requires a specific hardware setup. However, physical access opens up a straightforward attack vector: a malicious USB device emulating the FT260 can be plugged in. The attacker can then either wait for a privileged process to use the I2C bus or trigger the uninitialized stack memory bug by emulating an hwmon device.
|
| 83/1 |
2026/05/30 15:20 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a stack out-of-bounds read and write (stack buffer overflow) in the
`ft260_smbus_xfer` function of the `hid-ft260` driver.
When handling SMBus block transfers, the core I2C subsystem passes a `union
i2c_smbus_data` structure to the adapter's transfer function. This union
contains a `block` array of 34 bytes, where `block[0]` specifies the length of
the payload (which must be $\le$ `I2C_SMBUS_BLOCK_MAX`, i.e., 32).
The `ft260_smbus_xfer` function fails to validate that `data->block[0]` is
within this limit. It only checks if the length is less than the FT260 USB
report size (60 bytes).
This missing validation leads to two distinct memory corruption scenarios:
1. **Via `/dev/i2c-X` ioctl (OOB Read & Write):**
When a user calls the `I2C_SMBUS` ioctl, `i2cdev_ioctl_smbus` allocates a
34-byte `union i2c_smbus_data temp` on the stack and copies the user-provided
data into it, including `temp.block[0]`.
- For an `I2C_SMBUS_BLOCK_DATA` write, `ft260_smbus_xfer` will read
`data->block[0] + 1` bytes from the stack and send them to the USB device. If
the user sets `block[0] = 50`, it reads 51 bytes from the 34-byte stack
buffer, leaking kernel stack memory to the USB device.
- For an `I2C_SMBUS_I2C_BLOCK_DATA` read, `ft260_smbus_xfer` will read
`data->block[0]` bytes from the USB device and write them into `temp.block +
1`. If the user sets `block[0] = 255`, it writes 255 bytes into the 34-byte
stack buffer, causing a massive stack buffer overflow that can overwrite the
return address and lead to arbitrary code execution.
2. **Via Kernel Drivers (Uninitialized Stack Memory):**
If a kernel driver (e.g., a hardware monitoring driver like `lm93`) calls
`i2c_smbus_read_block_data`, the core I2C code allocates an **uninitialized**
`union i2c_smbus_data` on the stack. `ft260_smbus_xfer` incorrectly uses the
uninitialized `data->block[0]` as the number of bytes to request from the USB
device. If the uninitialized stack memory happens to contain a large value,
the USB device's response will overflow the stack buffer.
**Security Impact:**
* **Exploitable:** **Yes.** The stack buffer overflow allows an attacker to
overwrite the stack frame (including the return address), which is a classic
vector for arbitrary code execution. The out-of-bounds read can be used to
bypass KASLR or leak stack canaries.
* **Denial Of Service:** **Yes.** Overwriting the stack will inevitably lead to
a kernel panic and system crash.
* **Accessible From Unprivileged Processes:** **No.** The `/dev/i2c-X` device
nodes are restricted to `root` (or the `i2c` group) by default. Triggering the
bug via hwmon sysfs files requires a specific hardware setup (a vulnerable hwmon
chip connected to the FT260) and relies on uninitialized stack memory, which is
not a reliable vector for a typical unprivileged user on a standard system.
* **Accessible From User Namespaces:** **No.** I2C device nodes are not
accessible within unprivileged user namespaces.
* **Peripheral Trigger:** **Yes.** An attacker with physical access can plug in
a malicious USB device that emulates an FT260 bridge. They can then either wait
for a privileged process to interact with the I2C bus, or emulate an I2C hwmon
device to trigger the uninitialized stack memory bug when the system
automatically probes and reads from the emulated sensors.]
|