| 0/0 |
2026/08/09 08:50 |
flow |
assessment-security |
53m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in the `sur40` touchscreen driver,
specifically in the `sur40_open` function.
When a matching USB device is plugged in, the kernel invokes `sur40_probe`.
During initialization, the driver allocates a `sur40_state` structure (which is
around 3KB and falls into the `kmalloc-4k` slab cache). It then allocates and
registers an input device via `input_register_device(input)`.
As soon as `input_register_device` succeeds, the input device is exposed to
userspace (e.g., `/dev/input/eventX`), and uevents are sent. Userspace daemons
like `udevd` can immediately discover and open the device.
If a subsequent initialization step in `sur40_probe` fails (for example,
`video_register_device` failing due to minor number exhaustion or memory
pressure), the driver jumps to its error-handling path:
```c
err_free_input:
input_free_device(input);
err_free_dev:
kfree(sur40);
```
The bug lies in the use of `input_free_device(input)` instead of
`input_unregister_device(input)`. `input_free_device()` only drops the initial
reference taken during allocation, but because the device was successfully
registered, it remains active and visible to userspace. The driver then frees
the `sur40` structure via `kfree(sur40)`.
When `udevd` (or any other userspace process) opens the lingering input device,
the kernel calls `sur40_open`. This function retrieves the freed `sur40`
structure using `input_get_drvdata(input)` and passes it to `sur40_init`.
`sur40_init` then attempts to send USB control messages by dereferencing
`sur40->usbdev`, resulting in a Use-After-Free.
**Exploitability:**
This bug is **Exploitable**. The freed `sur40_state` object is allocated from
the `kmalloc-4k` cache. An attacker can use heap spraying techniques to
reallocate this 4KB chunk with controlled data before `sur40_open` is triggered.
When `sur40_open` calls `sur40_command`, it passes the attacker-controlled
`sur40->usbdev` pointer to `usb_control_msg()`. The USB core will dereference
this controlled pointer, which provides a strong primitive for arbitrary memory
read/write or control-flow hijacking (e.g., by faking the `usb_device` structure
and its internal function pointers), leading to local privilege escalation.
**Triggering:**
This is a **Peripheral Trigger**. To trigger the bug, an attacker needs to plug
in a malicious USB device that passes the initial endpoint checks in
`sur40_probe` but causes a later initialization step to fail. This can be
reliably achieved by plugging in multiple such devices to exhaust the available
video device minor numbers (64 for `VFL_TYPE_TOUCH`), causing
`video_register_device` to fail on the last one and triggering the erroneous
cleanup path.
Because it requires plugging in a physical USB device (or having root access to
emulate one via `/dev/raw-gadget`), it is not accessible from unprivileged
processes or user namespaces. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/08/09 08:50 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
static unsigned long long procid;
static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}
#define MAX_FDS 30
#define USB_MAX_IFACE_NUM 4
#define USB_MAX_EP_NUM 32
#define USB_MAX_FDS 6
struct usb_endpoint_index {
struct usb_endpoint_descriptor desc;
int handle;
};
struct usb_iface_index {
struct usb_interface_descriptor* iface;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bInterfaceClass;
struct usb_endpoint_index eps[USB_MAX_EP_NUM];
int eps_num;
};
struct usb_device_index {
struct usb_device_descriptor* dev;
struct usb_config_descriptor* config;
uint8_t bDeviceClass;
uint8_t bMaxPower;
int config_length;
struct usb_iface_index ifaces[USB_MAX_IFACE_NUM];
int ifaces_num;
int iface_cur;
};
struct usb_info {
int fd;
struct usb_device_index index;
};
static struct usb_info usb_devices[USB_MAX_FDS];
static struct usb_device_index* lookup_usb_index(int fd)
{
for (int i = 0; i < USB_MAX_FDS; i++) {
if (__atomic_load_n(&usb_devices[i].fd, __ATOMIC_ACQUIRE) == fd)
return &usb_devices[i].index;
}
return NULL;
}
static int usb_devices_num;
static bool parse_usb_descriptor(const char* buffer, size_t length, struct usb_device_index* index)
{
if (length < sizeof(*index->dev) + sizeof(*index->config))
return false;
memset(index, 0, sizeof(*index));
index->dev = (struct usb_device_descriptor*)buffer;
index->config = (struct usb_config_descriptor*)(buffer + sizeof(*index->dev));
index->bDeviceClass = index->dev->bDeviceClass;
index->bMaxPower = index->config->bMaxPower;
index->config_length = length - sizeof(*index->dev);
index->iface_cur = -1;
size_t offset = 0;
while (true) {
if (offset + 1 >= length)
break;
uint8_t desc_length = buffer[offset];
uint8_t desc_type = buffer[offset + 1];
if (desc_length <= 2)
break;
if (offset + desc_length > length)
break;
if (desc_type == USB_DT_INTERFACE && index->ifaces_num < USB_MAX_IFACE_NUM) {
struct usb_interface_descriptor* iface = (struct usb_interface_descriptor*)(buffer + offset);
index->ifaces[index->ifaces_num].iface = iface;
index->ifaces[index->ifaces_num].bInterfaceNumber = iface->bInterfaceNumber;
index->ifaces[index->ifaces_num].bAlternateSetting = iface->bAlternateSetting;
index->ifaces[index->ifaces_num].bInterfaceClass = iface->bInterfaceClass;
index->ifaces_num++;
}
if (desc_type == USB_DT_ENDPOINT && index->ifaces_num > 0) {
struct usb_iface_index* iface = &index->ifaces[index->ifaces_num - 1];
if (iface->eps_num < USB_MAX_EP_NUM) {
memcpy(&iface->eps[iface->eps_num].desc, buffer + offset, sizeof(iface->eps[iface->eps_num].desc));
iface->eps_num++;
}
}
offset += desc_length;
}
return true;
}
static struct usb_device_index* add_usb_index(int fd, const char* dev, size_t dev_len)
{
int i = __atomic_fetch_add(&usb_devices_num, 1, __ATOMIC_RELAXED);
if (i >= USB_MAX_FDS)
return NULL;
if (!parse_usb_descriptor(dev, dev_len, &usb_devices[i].index))
return NULL;
__atomic_store_n(&usb_devices[i].fd, fd, __ATOMIC_RELEASE);
return &usb_devices[i].index;
}
struct vusb_connect_string_descriptor {
uint32_t len;
char* str;
} __attribute__((packed));
struct vusb_connect_descriptors {
uint32_t qual_len;
char* qual;
uint32_t bos_len;
char* bos;
uint32_t strs_len;
struct vusb_connect_string_descriptor strs[0];
} __attribute__((packed));
static const char default_string[] = {
8, USB_DT_STRING,
's', 0, 'y', 0, 'z', 0
};
static const char default_lang_id[] = {
4, USB_DT_STRING,
0x09, 0x04
};
static bool lookup_connect_response_in(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl,
struct usb_qualifier_descriptor* qual,
char** response_data, uint32_t* response_length)
{
struct usb_device_index* index = lookup_usb_index(fd);
uint8_t str_idx;
if (!index)
return false;
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_GET_DESCRIPTOR:
switch (ctrl->wValue >> 8) {
case USB_DT_DEVICE:
*response_data = (char*)index->dev;
*response_length = sizeof(*index->dev);
return true;
case USB_DT_CONFIG:
*response_data = (char*)index->config;
*response_length = index->config_length;
return true;
case USB_DT_STRING:
str_idx = (uint8_t)ctrl->wValue;
if (descs && str_idx < descs->strs_len) {
*response_data = descs->strs[str_idx].str;
*response_length = descs->strs[str_idx].len;
return true;
}
if (str_idx == 0) {
*response_data = (char*)&default_lang_id[0];
*response_length = default_lang_id[0];
return true;
}
*response_data = (char*)&default_string[0];
*response_length = default_string[0];
return true;
case USB_DT_BOS:
*response_data = descs->bos;
*response_length = descs->bos_len;
return true;
case USB_DT_DEVICE_QUALIFIER:
if (!descs->qual) {
qual->bLength = sizeof(*qual);
qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
qual->bcdUSB = index->dev->bcdUSB;
qual->bDeviceClass = index->dev->bDeviceClass;
qual->bDeviceSubClass = index->dev->bDeviceSubClass;
qual->bDeviceProtocol = index->dev->bDeviceProtocol;
qual->bMaxPacketSize0 = index->dev->bMaxPacketSize0;
qual->bNumConfigurations = index->dev->bNumConfigurations;
qual->bRESERVED = 0;
*response_data = (char*)qual;
*response_length = sizeof(*qual);
return true;
}
*response_data = descs->qual;
*response_length = descs->qual_len;
return true;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return false;
}
typedef bool (*lookup_connect_out_response_t)(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done);
static bool lookup_connect_response_out_generic(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done)
{
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_SET_CONFIGURATION:
*done = true;
return true;
default:
break;
}
break;
}
return false;
}
#define UDC_NAME_LENGTH_MAX 128
struct usb_raw_init {
__u8 driver_name[UDC_NAME_LENGTH_MAX];
__u8 device_name[UDC_NAME_LENGTH_MAX];
__u8 speed;
};
enum usb_raw_event_type {
USB_RAW_EVENT_INVALID = 0,
USB_RAW_EVENT_CONNECT = 1,
USB_RAW_EVENT_CONTROL = 2,
};
struct usb_raw_event {
__u32 type;
__u32 length;
__u8 data[0];
};
struct usb_raw_ep_io {
__u16 ep;
__u16 flags;
__u32 length;
__u8 data[0];
};
#define USB_RAW_EPS_NUM_MAX 30
#define USB_RAW_EP_NAME_MAX 16
#define USB_RAW_EP_ADDR_ANY 0xff
struct usb_raw_ep_caps {
__u32 type_control : 1;
__u32 type_iso : 1;
__u32 type_bulk : 1;
__u32 type_int : 1;
__u32 dir_in : 1;
__u32 dir_out : 1;
};
struct usb_raw_ep_limits {
__u16 maxpacket_limit;
__u16 max_streams;
__u32 reserved;
};
struct usb_raw_ep_info {
__u8 name[USB_RAW_EP_NAME_MAX];
__u32 addr;
struct usb_raw_ep_caps caps;
struct usb_raw_ep_limits limits;
};
struct usb_raw_eps_info {
struct usb_raw_ep_info eps[USB_RAW_EPS_NUM_MAX];
};
#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init)
#define USB_RAW_IOCTL_RUN _IO('U', 1)
#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event)
#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor)
#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32)
#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9)
#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32)
#define USB_RAW_IOCTL_EPS_INFO _IOR('U', 11, struct usb_raw_eps_info)
#define USB_RAW_IOCTL_EP0_STALL _IO('U', 12)
#define USB_RAW_IOCTL_EP_SET_HALT _IOW('U', 13, __u32)
#define USB_RAW_IOCTL_EP_CLEAR_HALT _IOW('U', 14, __u32)
#define USB_RAW_IOCTL_EP_SET_WEDGE _IOW('U', 15, __u32)
static int usb_raw_open()
{
return open("/dev/raw-gadget", O_RDWR);
}
static int usb_raw_init(int fd, uint32_t speed, const char* driver, const char* device)
{
struct usb_raw_init arg;
strncpy((char*)&arg.driver_name[0], driver, sizeof(arg.driver_name));
strncpy((char*)&arg.device_name[0], device, sizeof(arg.device_name));
arg.speed = speed;
return ioctl(fd, USB_RAW_IOCTL_INIT, &arg);
}
static int usb_raw_run(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_RUN, 0);
}
static int usb_raw_configure(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0);
}
static int usb_raw_vbus_draw(int fd, uint32_t power)
{
return ioctl(fd, USB_RAW_IOCTL_VBUS_DRAW, power);
}
static int usb_raw_ep0_write(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io);
}
static int usb_raw_ep0_read(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_READ, io);
}
static int usb_raw_event_fetch(int fd, struct usb_raw_event* event)
{
return ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event);
}
static int usb_raw_ep_enable(int fd, struct usb_endpoint_descriptor* desc)
{
return ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, desc);
}
static int usb_raw_ep_disable(int fd, int ep)
{
return ioctl(fd, USB_RAW_IOCTL_EP_DISABLE, ep);
}
static int usb_raw_ep0_stall(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0);
}
#define USB_MAX_PACKET_SIZE 4096
struct usb_raw_control_event {
struct usb_raw_event inner;
struct usb_ctrlrequest ctrl;
char data[USB_MAX_PACKET_SIZE];
};
struct usb_raw_ep_io_data {
struct usb_raw_ep_io inner;
char data[USB_MAX_PACKET_SIZE];
};
static void set_interface(int fd, int n)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return;
if (index->iface_cur >= 0 && index->iface_cur < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[index->iface_cur].eps_num; ep++) {
int rv = usb_raw_ep_disable(fd, index->ifaces[index->iface_cur].eps[ep].handle);
if (rv < 0) {
} else {
}
}
}
if (n >= 0 && n < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[n].eps_num; ep++) {
int rv = usb_raw_ep_enable(fd, &index->ifaces[n].eps[ep].desc);
if (rv < 0) {
} else {
index->ifaces[n].eps[ep].handle = rv;
}
}
index->iface_cur = n;
}
}
static int configure_device(int fd)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
int rv = usb_raw_vbus_draw(fd, index->bMaxPower);
if (rv < 0) {
return rv;
}
rv = usb_raw_configure(fd);
if (rv < 0) {
return rv;
}
set_interface(fd, 0);
return 0;
}
static volatile long syz_usb_connect_impl(uint64_t speed, uint64_t dev_len, const char* dev,
const struct vusb_connect_descriptors* descs,
lookup_connect_out_response_t lookup_connect_response_out)
{
if (!dev) {
return -1;
}
int fd = usb_raw_open();
if (fd < 0) {
return fd;
}
if (fd >= MAX_FDS) {
close(fd);
return -1;
}
struct usb_device_index* index = add_usb_index(fd, dev, dev_len);
if (!index) {
return -1;
}
char device[32];
sprintf(&device[0], "dummy_udc.%llu", procid);
int rv = usb_raw_init(fd, speed, "dummy_udc", &device[0]);
if (rv < 0) {
return rv;
}
rv = usb_raw_run(fd);
if (rv < 0) {
return rv;
}
bool done = false;
while (!done) {
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = sizeof(event.ctrl);
rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL)
continue;
char* response_data = NULL;
uint32_t response_length = 0;
struct usb_qualifier_descriptor qual;
if (event.ctrl.bRequestType & USB_DIR_IN) {
if (!lookup_connect_response_in(fd, descs, &event.ctrl, &qual, &response_data, &response_length)) {
usb_raw_ep0_stall(fd);
continue;
}
} else {
if (!lookup_connect_response_out(fd, descs, &event.ctrl, &done)) {
usb_raw_ep0_stall(fd);
continue;
}
response_data = NULL;
response_length = event.ctrl.wLength;
}
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
event.ctrl.bRequest == USB_REQ_SET_CONFIGURATION) {
rv = configure_device(fd);
if (rv < 0) {
return rv;
}
}
struct usb_raw_ep_io_data response;
response.inner.ep = 0;
response.inner.flags = 0;
if (response_length > sizeof(response.data))
response_length = 0;
if (event.ctrl.wLength < response_length)
response_length = event.ctrl.wLength;
response.inner.length = response_length;
if (response_data)
memcpy(&response.data[0], response_data, response_length);
else
memset(&response.data[0], 0, response_length);
if (event.ctrl.bRequestType & USB_DIR_IN) {
rv = usb_raw_ep0_write(fd, (struct usb_raw_ep_io*)&response);
} else {
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io*)&response);
}
if (rv < 0) {
return rv;
}
}
sleep_ms(200);
return fd;
}
static volatile long syz_usb_connect(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
{
uint64_t speed = a0;
uint64_t dev_len = a1;
const char* dev = (const char*)a2;
const struct vusb_connect_descriptors* descs = (const struct vusb_connect_descriptors*)a3;
return syz_usb_connect_impl(speed, dev_len, dev, descs, &lookup_connect_response_out_generic);
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000, /*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=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_usb_connect arguments: [
// speed: usb_device_speed = 0x0 (8 bytes)
// dev_len: len = 0x47 (8 bytes)
// dev: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {12 01 00 02 ff 00 00 40 5e 04 75 07 00 01 01 02 00 01 09 02 35 00 01 01 00 a0 32 09 04 00 00 05 ff 00 00 00 07 05 81 02 00 02 00 07 05 82 02 00 02 00 07 05 83 02 00 02 00 07 05 84 02 00 02 00 07 05 86 02 00 02 00} (length 0x47)
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb
memcpy((void*)0x200000000100, "... [truncated large byte array] ...", 71);
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x47, /*dev=*/0x200000000100, /*conn_descs=*/0);
return 0;
}
]
|
| 2/1 |
2026/08/09 08:50 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/ab831c5afa3b96adfab9e3e8fdf4b69fc7dd87fb]
|
| 3/1 |
2026/08/09 08:51 |
action |
kernel-builder |
25m
Results: map[KernelObj:/app/workdir/cache/build/7cedd79e4f889980614b3ed75501b6330da3c591]
|
| 4/1 |
2026/08/09 09:17 |
action |
codesearch-prepare |
18m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/09 09:35 |
agent |
expert |
8m
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, elevated privileges, or an information leak.
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.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
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: slab-use-after-free in sur40_init drivers/input/touchscreen/sur40.c:352 [inline]
BUG: KASAN: slab-use-after-free in sur40_open+0x46c/0x500 drivers/input/touchscreen/sur40.c:397
Read of size 8 at addr ffff88807a69a000 by task udevd/5642
CPU: 0 UID: 0 PID: 5642 Comm: udevd Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x13d/0x4b0 mm/kasan/report.c:482
kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
sur40_init drivers/input/touchscreen/sur40.c:352 [inline]
sur40_open+0x46c/0x500 drivers/input/touchscreen/sur40.c:397
input_open_device+0x24c/0x3d0 drivers/input/input.c:601
evdev_open_device drivers/input/evdev.c:391 [inline]
evdev_open+0x52f/0x690 drivers/input/evdev.c:478
chrdev_open+0x234/0x6a0 fs/char_dev.c:411
do_dentry_open+0x6ab/0x14d0 fs/open.c:947
vfs_open+0x82/0x3f0 fs/open.c:1052
do_open fs/namei.c:4700 [inline]
path_openat+0x2873/0x4280 fs/namei.c:4863
do_file_open+0x20e/0x430 fs/namei.c:4892
do_sys_openat2+0x10f/0x1e0 fs/open.c:1368
do_sys_open fs/open.c:1374 [inline]
__do_sys_openat fs/open.c:1390 [inline]
__se_sys_openat fs/open.c:1385 [inline]
__x64_sys_openat+0x12d/0x210 fs/open.c:1385
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f06732a7407
Code: 48 89 fa 4c 89 df e8 38 aa 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
RSP: 002b:00007fffba81d070 EFLAGS: 00000202 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 00007f0673a07880 RCX: 00007f06732a7407
RDX: 0000000000080000 RSI: 0000560bc61336f0 RDI: ffffffffffffff9c
RBP: 0000560bc6115500 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000202 R12: 0000560b97aa50c8
R13: 0000560b97aa50ce R14: 00007fffba81d670 R15: 0000560b97aabbcc
</TASK>
Allocated by task 5634:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x2e5/0x6c0 mm/slub.c:5489
_kmalloc_noprof include/linux/slab.h:988 [inline]
_kzalloc_noprof include/linux/slab.h:1309 [inline]
sur40_probe+0x1a8/0x1580 drivers/input/touchscreen/sur40.c:675
usb_probe_interface+0x303/0x8f0 drivers/usb/core/driver.c:396
call_driver_probe drivers/base/dd.c:628 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:706
__driver_probe_device+0x20e/0x450 drivers/base/dd.c:868
driver_probe_device+0x4a/0x140 drivers/base/dd.c:898
__device_attach_driver+0x1df/0x320 drivers/base/dd.c:1026
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1098
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1153
bus_probe_device+0x64/0x160 drivers/base/bus.c:620
device_add+0x121d/0x1970 drivers/base/core.c:3772
usb_set_configuration+0xd97/0x1c60 drivers/usb/core/message.c:2268
usb_generic_driver_probe+0xa1/0xe0 drivers/usb/core/generic.c:250
usb_probe_device+0xef/0x400 drivers/usb/core/driver.c:291
call_driver_probe drivers/base/dd.c:628 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:706
__driver_probe_device+0x20e/0x450 drivers/base/dd.c:868
driver_probe_device+0x4a/0x140 drivers/base/dd.c:898
__device_attach_driver+0x1df/0x320 drivers/base/dd.c:1026
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1098
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1153
bus_probe_device+0x64/0x160 drivers/base/bus.c:620
device_add+0x121d/0x1970 drivers/base/core.c:3772
usb_new_device.cold+0x685/0x115c drivers/usb/core/hub.c:2695
hub_port_connect drivers/usb/core/hub.c:5567 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
port_event drivers/usb/core/hub.c:5871 [inline]
hub_event+0x30a3/0x4a60 drivers/usb/core/hub.c:5953
process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
process_scheduled_works kernel/workqueue.c:3405 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Freed by task 5634:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2677 [inline]
slab_free mm/slub.c:6377 [inline]
kfree+0x22b/0x6c0 mm/slub.c:6692
sur40_probe+0xc36/0x1580 drivers/input/touchscreen/sur40.c:816
usb_probe_interface+0x303/0x8f0 drivers/usb/core/driver.c:396
call_driver_probe drivers/base/dd.c:628 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:706
__driver_probe_device+0x20e/0x450 drivers/base/dd.c:868
driver_probe_device+0x4a/0x140 drivers/base/dd.c:898
__device_attach_driver+0x1df/0x320 drivers/base/dd.c:1026
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1098
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1153
bus_probe_device+0x64/0x160 drivers/base/bus.c:620
device_add+0x121d/0x1970 drivers/base/core.c:3772
usb_set_configuration+0xd97/0x1c60 drivers/usb/core/message.c:2268
usb_generic_driver_probe+0xa1/0xe0 drivers/usb/core/generic.c:250
usb_probe_device+0xef/0x400 drivers/usb/core/driver.c:291
call_driver_probe drivers/base/dd.c:628 [inline]
really_probe+0x241/0xa60 drivers/base/dd.c:706
__driver_probe_device+0x20e/0x450 drivers/base/dd.c:868
driver_probe_device+0x4a/0x140 drivers/base/dd.c:898
__device_attach_driver+0x1df/0x320 drivers/base/dd.c:1026
bus_for_each_drv+0x159/0x1e0 drivers/base/bus.c:500
__device_attach+0x1e4/0x4d0 drivers/base/dd.c:1098
device_initial_probe+0xaf/0xd0 drivers/base/dd.c:1153
bus_probe_device+0x64/0x160 drivers/base/bus.c:620
device_add+0x121d/0x1970 drivers/base/core.c:3772
usb_new_device.cold+0x685/0x115c drivers/usb/core/hub.c:2695
hub_port_connect drivers/usb/core/hub.c:5567 [inline]
hub_port_connect_change drivers/usb/core/hub.c:5707 [inline]
port_event drivers/usb/core/hub.c:5871 [inline]
hub_event+0x30a3/0x4a60 drivers/usb/core/hub.c:5953
process_one_work+0xa23/0x1940 kernel/workqueue.c:3322
process_scheduled_works kernel/workqueue.c:3405 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3486
kthread+0x370/0x450 kernel/kthread.c:436
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
The buggy address belongs to the object at ffff88807a69a000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 0 bytes inside of
freed 4096-byte region [ffff88807a69a000, ffff88807a69b000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x7a698
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff88813fe28140 dead000000000100 dead000000000122
raw: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff88813fe28140 dead000000000100 dead000000000122
head: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
head: 00fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 4985, tgid 4985 (udevd), ts 22813655696, free_ts 18627306281
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1859
prep_new_page mm/page_alloc.c:1867 [inline]
get_page_from_freelist+0xf48/0x3530 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x299/0x2dc0 mm/page_alloc.c:5304
alloc_slab_page mm/slub.c:3266 [inline]
allocate_slab mm/slub.c:3380 [inline]
new_slab+0xa2/0x640 mm/slub.c:3426
refill_objects+0xe3/0x410 mm/slub.c:7310
refill_sheaf mm/slub.c:2804 [inline]
__pcs_replace_empty_main+0x376/0x680 mm/slub.c:4675
alloc_from_pcs mm/slub.c:4773 [inline]
slab_alloc_node mm/slub.c:4905 [inline]
__do_kmalloc_node mm/slub.c:5333 [inline]
__kmalloc_noprof+0x66d/0x820 mm/slub.c:5359
_kmalloc_noprof include/linux/slab.h:992 [inline]
tomoyo_realpath_from_path+0xb6/0x690 security/tomoyo/realpath.c:251
tomoyo_get_realpath security/tomoyo/file.c:151 [inline]
tomoyo_check_open_permission+0x2af/0x3c0 security/tomoyo/file.c:776
tomoyo_file_open+0x6b/0x90 security/tomoyo/tomoyo.c:334
security_file_open+0xb5/0x1e0 security/security.c:2739
do_dentry_open+0x588/0x14d0 fs/open.c:924
vfs_open+0x82/0x3f0 fs/open.c:1052
do_open fs/namei.c:4700 [inline]
path_openat+0x2873/0x4280 fs/namei.c:4863
do_file_open+0x20e/0x430 fs/namei.c:4892
do_sys_openat2+0x10f/0x1e0 fs/open.c:1368
page last free pid 1 tgid 1 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1406 [inline]
free_pages_prepare+0x586/0xd80 mm/page_alloc.c:1451
__free_contig_range_common+0x14f/0x250 mm/page_alloc.c:6897
debug_vm_pgtable_free_huge_page mm/debug_vm_pgtable.c:980 [inline]
destroy_args+0xa8/0x7a0 mm/debug_vm_pgtable.c:993
debug_vm_pgtable+0x1d69/0x3490 mm/debug_vm_pgtable.c:1368
do_one_initcall+0x11d/0x700 init/main.c:1347
do_initcall_level init/main.c:1409 [inline]
do_initcalls init/main.c:1425 [inline]
do_basic_setup init/main.c:1445 [inline]
kernel_init_freeable+0x6ea/0x7b0 init/main.c:1658
kernel_init+0x1f/0x1e0 init/main.c:1548
ret_from_fork+0x72b/0xd50 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff88807a699f00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff88807a699f80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff88807a69a000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88807a69a080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807a69a100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
static unsigned long long procid;
static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}
#define MAX_FDS 30
#define USB_MAX_IFACE_NUM 4
#define USB_MAX_EP_NUM 32
#define USB_MAX_FDS 6
struct usb_endpoint_index {
struct usb_endpoint_descriptor desc;
int handle;
};
struct usb_iface_index {
struct usb_interface_descriptor* iface;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bInterfaceClass;
struct usb_endpoint_index eps[USB_MAX_EP_NUM];
int eps_num;
};
struct usb_device_index {
struct usb_device_descriptor* dev;
struct usb_config_descriptor* config;
uint8_t bDeviceClass;
uint8_t bMaxPower;
int config_length;
struct usb_iface_index ifaces[USB_MAX_IFACE_NUM];
int ifaces_num;
int iface_cur;
};
struct usb_info {
int fd;
struct usb_device_index index;
};
static struct usb_info usb_devices[USB_MAX_FDS];
static struct usb_device_index* lookup_usb_index(int fd)
{
for (int i = 0; i < USB_MAX_FDS; i++) {
if (__atomic_load_n(&usb_devices[i].fd, __ATOMIC_ACQUIRE) == fd)
return &usb_devices[i].index;
}
return NULL;
}
static int usb_devices_num;
static bool parse_usb_descriptor(const char* buffer, size_t length, struct usb_device_index* index)
{
if (length < sizeof(*index->dev) + sizeof(*index->config))
return false;
memset(index, 0, sizeof(*index));
index->dev = (struct usb_device_descriptor*)buffer;
index->config = (struct usb_config_descriptor*)(buffer + sizeof(*index->dev));
index->bDeviceClass = index->dev->bDeviceClass;
index->bMaxPower = index->config->bMaxPower;
index->config_length = length - sizeof(*index->dev);
index->iface_cur = -1;
size_t offset = 0;
while (true) {
if (offset + 1 >= length)
break;
uint8_t desc_length = buffer[offset];
uint8_t desc_type = buffer[offset + 1];
if (desc_length <= 2)
break;
if (offset + desc_length > length)
break;
if (desc_type == USB_DT_INTERFACE && index->ifaces_num < USB_MAX_IFACE_NUM) {
struct usb_interface_descriptor* iface = (struct usb_interface_descriptor*)(buffer + offset);
index->ifaces[index->ifaces_num].iface = iface;
index->ifaces[index->ifaces_num].bInterfaceNumber = iface->bInterfaceNumber;
index->ifaces[index->ifaces_num].bAlternateSetting = iface->bAlternateSetting;
index->ifaces[index->ifaces_num].bInterfaceClass = iface->bInterfaceClass;
index->ifaces_num++;
}
if (desc_type == USB_DT_ENDPOINT && index->ifaces_num > 0) {
struct usb_iface_index* iface = &index->ifaces[index->ifaces_num - 1];
if (iface->eps_num < USB_MAX_EP_NUM) {
memcpy(&iface->eps[iface->eps_num].desc, buffer + offset, sizeof(iface->eps[iface->eps_num].desc));
iface->eps_num++;
}
}
offset += desc_length;
}
return true;
}
static struct usb_device_index* add_usb_index(int fd, const char* dev, size_t dev_len)
{
int i = __atomic_fetch_add(&usb_devices_num, 1, __ATOMIC_RELAXED);
if (i >= USB_MAX_FDS)
return NULL;
if (!parse_usb_descriptor(dev, dev_len, &usb_devices[i].index))
return NULL;
__atomic_store_n(&usb_devices[i].fd, fd, __ATOMIC_RELEASE);
return &usb_devices[i].index;
}
struct vusb_connect_string_descriptor {
uint32_t len;
char* str;
} __attribute__((packed));
struct vusb_connect_descriptors {
uint32_t qual_len;
char* qual;
uint32_t bos_len;
char* bos;
uint32_t strs_len;
struct vusb_connect_string_descriptor strs[0];
} __attribute__((packed));
static const char default_string[] = {
8, USB_DT_STRING,
's', 0, 'y', 0, 'z', 0
};
static const char default_lang_id[] = {
4, USB_DT_STRING,
0x09, 0x04
};
static bool lookup_connect_response_in(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl,
struct usb_qualifier_descriptor* qual,
char** response_data, uint32_t* response_length)
{
struct usb_device_index* index = lookup_usb_index(fd);
uint8_t str_idx;
if (!index)
return false;
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_GET_DESCRIPTOR:
switch (ctrl->wValue >> 8) {
case USB_DT_DEVICE:
*response_data = (char*)index->dev;
*response_length = sizeof(*index->dev);
return true;
case USB_DT_CONFIG:
*response_data = (char*)index->config;
*response_length = index->config_length;
return true;
case USB_DT_STRING:
str_idx = (uint8_t)ctrl->wValue;
if (descs && str_idx < descs->strs_len) {
*response_data = descs->strs[str_idx].str;
*response_length = descs->strs[str_idx].len;
return true;
}
if (str_idx == 0) {
*response_data = (char*)&default_lang_id[0];
*response_length = default_lang_id[0];
return true;
}
*response_data = (char*)&default_string[0];
*response_length = default_string[0];
return true;
case USB_DT_BOS:
*response_data = descs->bos;
*response_length = descs->bos_len;
return true;
case USB_DT_DEVICE_QUALIFIER:
if (!descs->qual) {
qual->bLength = sizeof(*qual);
qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
qual->bcdUSB = index->dev->bcdUSB;
qual->bDeviceClass = index->dev->bDeviceClass;
qual->bDeviceSubClass = index->dev->bDeviceSubClass;
qual->bDeviceProtocol = index->dev->bDeviceProtocol;
qual->bMaxPacketSize0 = index->dev->bMaxPacketSize0;
qual->bNumConfigurations = index->dev->bNumConfigurations;
qual->bRESERVED = 0;
*response_data = (char*)qual;
*response_length = sizeof(*qual);
return true;
}
*response_data = descs->qual;
*response_length = descs->qual_len;
return true;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return false;
}
typedef bool (*lookup_connect_out_response_t)(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done);
static bool lookup_connect_response_out_generic(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done)
{
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_SET_CONFIGURATION:
*done = true;
return true;
default:
break;
}
break;
}
return false;
}
#define UDC_NAME_LENGTH_MAX 128
struct usb_raw_init {
__u8 driver_name[UDC_NAME_LENGTH_MAX];
__u8 device_name[UDC_NAME_LENGTH_MAX];
__u8 speed;
};
enum usb_raw_event_type {
USB_RAW_EVENT_INVALID = 0,
USB_RAW_EVENT_CONNECT = 1,
USB_RAW_EVENT_CONTROL = 2,
};
struct usb_raw_event {
__u32 type;
__u32 length;
__u8 data[0];
};
struct usb_raw_ep_io {
__u16 ep;
__u16 flags;
__u32 length;
__u8 data[0];
};
#define USB_RAW_EPS_NUM_MAX 30
#define USB_RAW_EP_NAME_MAX 16
#define USB_RAW_EP_ADDR_ANY 0xff
struct usb_raw_ep_caps {
__u32 type_control : 1;
__u32 type_iso : 1;
__u32 type_bulk : 1;
__u32 type_int : 1;
__u32 dir_in : 1;
__u32 dir_out : 1;
};
struct usb_raw_ep_limits {
__u16 maxpacket_limit;
__u16 max_streams;
__u32 reserved;
};
struct usb_raw_ep_info {
__u8 name[USB_RAW_EP_NAME_MAX];
__u32 addr;
struct usb_raw_ep_caps caps;
struct usb_raw_ep_limits limits;
};
struct usb_raw_eps_info {
struct usb_raw_ep_info eps[USB_RAW_EPS_NUM_MAX];
};
#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init)
#define USB_RAW_IOCTL_RUN _IO('U', 1)
#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event)
#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor)
#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32)
#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9)
#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32)
#define USB_RAW_IOCTL_EPS_INFO _IOR('U', 11, struct usb_raw_eps_info)
#define USB_RAW_IOCTL_EP0_STALL _IO('U', 12)
#define USB_RAW_IOCTL_EP_SET_HALT _IOW('U', 13, __u32)
#define USB_RAW_IOCTL_EP_CLEAR_HALT _IOW('U', 14, __u32)
#define USB_RAW_IOCTL_EP_SET_WEDGE _IOW('U', 15, __u32)
static int usb_raw_open()
{
return open("/dev/raw-gadget", O_RDWR);
}
static int usb_raw_init(int fd, uint32_t speed, const char* driver, const char* device)
{
struct usb_raw_init arg;
strncpy((char*)&arg.driver_name[0], driver, sizeof(arg.driver_name));
strncpy((char*)&arg.device_name[0], device, sizeof(arg.device_name));
arg.speed = speed;
return ioctl(fd, USB_RAW_IOCTL_INIT, &arg);
}
static int usb_raw_run(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_RUN, 0);
}
static int usb_raw_configure(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0);
}
static int usb_raw_vbus_draw(int fd, uint32_t power)
{
return ioctl(fd, USB_RAW_IOCTL_VBUS_DRAW, power);
}
static int usb_raw_ep0_write(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io);
}
static int usb_raw_ep0_read(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_READ, io);
}
static int usb_raw_event_fetch(int fd, struct usb_raw_event* event)
{
return ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event);
}
static int usb_raw_ep_enable(int fd, struct usb_endpoint_descriptor* desc)
{
return ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, desc);
}
static int usb_raw_ep_disable(int fd, int ep)
{
return ioctl(fd, USB_RAW_IOCTL_EP_DISABLE, ep);
}
static int usb_raw_ep0_stall(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0);
}
#define USB_MAX_PACKET_SIZE 4096
struct usb_raw_control_event {
struct usb_raw_event inner;
struct usb_ctrlrequest ctrl;
char data[USB_MAX_PACKET_SIZE];
};
struct usb_raw_ep_io_data {
struct usb_raw_ep_io inner;
char data[USB_MAX_PACKET_SIZE];
};
static void set_interface(int fd, int n)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return;
if (index->iface_cur >= 0 && index->iface_cur < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[index->iface_cur].eps_num; ep++) {
int rv = usb_raw_ep_disable(fd, index->ifaces[index->iface_cur].eps[ep].handle);
if (rv < 0) {
} else {
}
}
}
if (n >= 0 && n < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[n].eps_num; ep++) {
int rv = usb_raw_ep_enable(fd, &index->ifaces[n].eps[ep].desc);
if (rv < 0) {
} else {
index->ifaces[n].eps[ep].handle = rv;
}
}
index->iface_cur = n;
}
}
static int configure_device(int fd)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
int rv = usb_raw_vbus_draw(fd, index->bMaxPower);
if (rv < 0) {
return rv;
}
rv = usb_raw_configure(fd);
if (rv < 0) {
return rv;
}
set_interface(fd, 0);
return 0;
}
static volatile long syz_usb_connect_impl(uint64_t speed, uint64_t dev_len, const char* dev,
const struct vusb_connect_descriptors* descs,
lookup_connect_out_response_t lookup_connect_response_out)
{
if (!dev) {
return -1;
}
int fd = usb_raw_open();
if (fd < 0) {
return fd;
}
if (fd >= MAX_FDS) {
close(fd);
return -1;
}
struct usb_device_index* index = add_usb_index(fd, dev, dev_len);
if (!index) {
return -1;
}
char device[32];
sprintf(&device[0], "dummy_udc.%llu", procid);
int rv = usb_raw_init(fd, speed, "dummy_udc", &device[0]);
if (rv < 0) {
return rv;
}
rv = usb_raw_run(fd);
if (rv < 0) {
return rv;
}
bool done = false;
while (!done) {
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = sizeof(event.ctrl);
rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL)
continue;
char* response_data = NULL;
uint32_t response_length = 0;
struct usb_qualifier_descriptor qual;
if (event.ctrl.bRequestType & USB_DIR_IN) {
if (!lookup_connect_response_in(fd, descs, &event.ctrl, &qual, &response_data, &response_length)) {
usb_raw_ep0_stall(fd);
continue;
}
} else {
if (!lookup_connect_response_out(fd, descs, &event.ctrl, &done)) {
usb_raw_ep0_stall(fd);
continue;
}
response_data = NULL;
response_length = event.ctrl.wLength;
}
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
event.ctrl.bRequest == USB_REQ_SET_CONFIGURATION) {
rv = configure_device(fd);
if (rv < 0) {
return rv;
}
}
struct usb_raw_ep_io_data response;
response.inner.ep = 0;
response.inner.flags = 0;
if (response_length > sizeof(response.data))
response_length = 0;
if (event.ctrl.wLength < response_length)
response_length = event.ctrl.wLength;
response.inner.length = response_length;
if (response_data)
memcpy(&response.data[0], response_data, response_length);
else
memset(&response.data[0], 0, response_length);
if (event.ctrl.bRequestType & USB_DIR_IN) {
rv = usb_raw_ep0_write(fd, (struct usb_raw_ep_io*)&response);
} else {
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io*)&response);
}
if (rv < 0) {
return rv;
}
}
sleep_ms(200);
return fd;
}
static volatile long syz_usb_connect(volatile long a0, volatile long a1, volatile long a2, volatile long a3)
{
uint64_t speed = a0;
uint64_t dev_len = a1;
const char* dev = (const char*)a2;
const struct vusb_connect_descriptors* descs = (const struct vusb_connect_descriptors*)a3;
return syz_usb_connect_impl(speed, dev_len, dev, descs, &lookup_connect_response_out_generic);
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000, /*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=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// syz_usb_connect arguments: [
// speed: usb_device_speed = 0x0 (8 bytes)
// dev_len: len = 0x47 (8 bytes)
// dev: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {12 01 00 02 ff 00 00 40 5e 04 75 07 00 01 01 02 00 01 09 02 35 00 01 01 00 a0 32 09 04 00 00 05 ff 00 00 00 07 05 81 02 00 02 00 07 05 82 02 00 02 00 07 05 83 02 00 02 00 07 05 84 02 00 02 00 07 05 86 02 00 02 00} (length 0x47)
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb
memcpy((void*)0x200000000100, "... [truncated large byte array] ...", 71);
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x47, /*dev=*/0x200000000100, /*conn_descs=*/0);
return 0;
}
Reply: **Bug Analysis:**
The bug is a Use-After-Free (UAF) in the `sur40` touchscreen driver, specifically in the `sur40_open` function.
When a matching USB device is plugged in, the kernel invokes `sur40_probe`. During initialization, the driver allocates a `sur40_state` structure (which is around 3KB and falls into the `kmalloc-4k` slab cache). It then allocates and registers an input device via `input_register_device(input)`.
As soon as `input_register_device` succeeds, the input device is exposed to userspace (e.g., `/dev/input/eventX`), and uevents are sent. Userspace daemons like `udevd` can immediately discover and open the device.
If a subsequent initialization step in `sur40_probe` fails (for example, `video_register_device` failing due to minor number exhaustion or memory pressure), the driver jumps to its error-handling path:
```c
err_free_input:
input_free_device(input);
err_free_dev:
kfree(sur40);
```
The bug lies in the use of `input_free_device(input)` instead of `input_unregister_device(input)`. `input_free_device()` only drops the initial reference taken during allocation, but because the device was successfully registered, it remains active and visible to userspace. The driver then frees the `sur40` structure via `kfree(sur40)`.
When `udevd` (or any other userspace process) opens the lingering input device, the kernel calls `sur40_open`. This function retrieves the freed `sur40` structure using `input_get_drvdata(input)` and passes it to `sur40_init`. `sur40_init` then attempts to send USB control messages by dereferencing `sur40->usbdev`, resulting in a Use-After-Free.
**Exploitability:**
This bug is **Exploitable**. The freed `sur40_state` object is allocated from the `kmalloc-4k` cache. An attacker can use heap spraying techniques to reallocate this 4KB chunk with controlled data before `sur40_open` is triggered.
When `sur40_open` calls `sur40_command`, it passes the attacker-controlled `sur40->usbdev` pointer to `usb_control_msg()`. The USB core will dereference this controlled pointer, which provides a strong primitive for arbitrary memory read/write or control-flow hijacking (e.g., by faking the `usb_device` structure and its internal function pointers), leading to local privilege escalation.
**Triggering:**
This is a **Peripheral Trigger**. To trigger the bug, an attacker needs to plug in a malicious USB device that passes the initial endpoint checks in `sur40_probe` but causes a later initialization step to fail. This can be reliably achieved by plugging in multiple such devices to exhaust the available video device minor numbers (64 for `VFL_TYPE_TOUCH`), causing `video_register_device` to fail on the last one and triggering the erroneous cleanup path.
Because it requires plugging in a physical USB device (or having root access to emulate one via `/dev/raw-gadget`), it is not accessible from unprivileged processes or user namespaces.
|
| 6/2 |
2026/08/09 09:35 |
llm |
expert |
0m
Model:
Tokens:
input: 17853
output: 31
thoughts: 182
Thoughts: **Analyzing a KASAN Slab-Use-After-Free in the sur40 Driver**
Okay, so we've got a KASAN slab-use-after-free issue, and it's popping up in `sur40_init` which is called from `sur40_open`. Interesting. The bug report is pointing us right to the problem: a slab-use-after-free, specifically at line 352 (inlined) within `sur40_init` and then again at line 397 within the `sur40_open` function itself, in `drivers/input/touchscreen/sur40.c`.
A read of size 8 at address `ffff88807a69a000` by the `udevd` task is the triggering event. Right, so this is likely a race condition of some kind during device initialization, most likely during the open operation. My initial thought is to examine the allocation and deallocation patterns within the `sur40_init` and `sur40_open` functions, tracing back through the code to figure out what's being freed prematurely and then subsequently accessed. Gotta start digging into that `drivers/input/touchscreen/sur40.c` file and see what's going on in there. It's a classic case of either a missing memory fence, an incorrect reference counting scheme, or a use-after-free vulnerability due to premature resource release. Let's get to the source.
|
| 7/2 |
2026/08/09 09:35 |
tool |
codesearch-file-index |
0m
Args: map[SourceFile:drivers/input/touchscreen/sur40.c]
Results: map[Entities:[map[Kind:function Name:__check_brightness] map[Kind:function Name:__check_contrast] map[Kind:function Name:__check_gain] map[Kind:function Name:return_all_buffers] map[Kind:function Name:sur40_buffer_prepare] map[Kind:function Name:sur40_buffer_queue] map[Kind:function Name:sur40_close] map[Kind:function Name:sur40_command] map[Kind:function Name:sur40_disconnect] map[Kind:function Name:sur40_driver_exit] map[Kind:function Name:sur40_driver_init] map[Kind:function Name:sur40_init] map[Kind:function Name:sur40_input_setup_events] map[Kind:function Name:sur40_ioctl_parm] map[Kind:function Name:sur40_open] map[Kind:function Name:sur40_poke] map[Kind:function Name:sur40_poll] map[Kind:function Name:sur40_probe] map[Kind:function Name:sur40_process_video] map[Kind:function Name:sur40_queue_setup] map[Kind:function Name:sur40_report_blob] map[Kind:function Name:sur40_s_ctrl] map[Kind:function Name:sur40_set_irlevel] map[Kind:function Name:sur40_set_preprocessor] map[Kind:function Name:sur40_set_vsvideo] map[Kind:function Name:sur40_start_streaming] map[Kind:function Name:sur40_stop_streaming] map[Kind:function Name:sur40_vidioc_enum_fmt] map[Kind:function Name:sur40_vidioc_enum_frameintervals] map[Kind:function Name:sur40_vidioc_enum_framesizes] map[Kind:function Name:sur40_vidioc_enum_input] map[Kind:function Name:sur40_vidioc_g_fmt] map[Kind:function Name:sur40_vidioc_g_input] map[Kind:function Name:sur40_vidioc_querycap] map[Kind:function Name:sur40_vidioc_s_fmt] map[Kind:function Name:sur40_vidioc_s_input] map[Kind:function Name:sur40_vidioc_try_fmt] map[Kind:struct Name:sur40_blob] map[Kind:struct Name:sur40_buffer] map[Kind:struct Name:sur40_data] map[Kind:struct Name:sur40_header] map[Kind:struct Name:sur40_image_header] map[Kind:struct Name:sur40_state] map[Kind:global_variable Name:__UNIQUE_ID_addressable_sur40_driver_init_1185] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1123] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1124] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1125] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1126] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1127] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1128] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1186] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1187] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1188] map[Kind:global_variable Name:__UNIQUE_ID_modinfo_1189] map[Kind:global_variable Name:__exitcall_sur40_driver_exit] map[Kind:global_variable Name:__mod_device_table__kmod_sur40__usb__sur40_table] map[Kind:global_variable Name:__param_brightness] map[Kind:global_variable Name:__param_contrast] map[Kind:global_variable Name:__param_gain] map[Kind:global_variable Name:__param_str_brightness] map[Kind:global_variable Name:__param_str_contrast] map[Kind:global_variable Name:__param_str_gain] map[Kind:global_variable Name:brightness] map[Kind:global_variable Name:contrast] map[Kind:global_variable Name:gain] map[Kind:global_variable Name:sur40_ctrl_ops] map[Kind:global_variable Name:sur40_driver] map[Kind:global_variable Name:sur40_pix_format] map[Kind:global_variable Name:sur40_queue] map[Kind:global_variable Name:sur40_queue_ops] map[Kind:global_variable Name:sur40_table] map[Kind:global_variable Name:sur40_video_device] map[Kind:global_variable Name:sur40_video_fops] map[Kind:global_variable Name:sur40_video_ioctl_ops]]]
|
| 8/2 |
2026/08/09 09:35 |
llm |
expert |
0m
Model:
Tokens:
input: 19857
output: 41
thoughts: 19
Thoughts: Okay, let's dive into this. Here's how I'd approach that little mental check-in, as an expert:
**Internal Checklist for Initialization and Opening Procedures**
Right, so I need to quickly run through `sur40_init` and `sur40_open`. Just a mental scan, nothing too deep, but a solid sanity check. I need to make sure I'm not overlooking anything obvious, any potential gotchas. Are there any known issues I should be aware of with these particular routines? Maybe a recent patch or bug fix that subtly impacted initialization or the open process? Let's just run down the checklist: does initialization handle all the required resources? Memory allocation, file handles, device registration β is everything accounted for? And how about the `sur40_open` routine? Does it correctly validate input parameters, handle different access modes or permissions, and deal with any potential race conditions? Think error handling, particularly around resource conflicts. I want to make sure I've got a handle on the expected behavior with edge cases, too. Basically, I want to confirm that these two functions will perform as expected during standard use.
|
| 9/2 |
2026/08/09 09:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_init]
Results: map[SourceCode: 339: /* Initialization routine, called from sur40_open */
340: static int sur40_init(struct sur40_state *dev)
341: {
342: int result;
343: u8 *buffer;
344:
345: buffer = kmalloc(24, GFP_KERNEL);
346: if (!buffer) {
347: result = -ENOMEM;
348: goto error;
349: }
350:
351: /* stupidly replay the original MS driver init sequence */
352: result = sur40_command(dev, SUR40_GET_VERSION, 0x00, buffer, 12);
353: if (result < 0)
354: goto error;
355:
356: result = sur40_command(dev, SUR40_GET_VERSION, 0x01, buffer, 12);
357: if (result < 0)
358: goto error;
359:
360: result = sur40_command(dev, SUR40_GET_VERSION, 0x02, buffer, 12);
361: if (result < 0)
362: goto error;
363:
364: result = sur40_command(dev, SUR40_SENSOR_CAPS, 0x00, buffer, 24);
365: if (result < 0)
366: goto error;
367:
368: result = sur40_command(dev, SUR40_ACCEL_CAPS, 0x00, buffer, 5);
369: if (result < 0)
370: goto error;
371:
372: result = sur40_command(dev, SUR40_GET_VERSION, 0x03, buffer, 12);
373: if (result < 0)
374: goto error;
375:
376: result = 0;
377:
378: /*
379: * Discard the result buffer - no known data inside except
380: * some version strings, maybe extract these sometime...
381: */
382: error:
383: kfree(buffer);
384: return result;
385: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 10/2 |
2026/08/09 09:35 |
llm |
expert |
0m
Model:
Tokens:
input: 20882
output: 41
thoughts: 62
Thoughts: Here's my analysis of the provided information, as if these were my internal thoughts:
**Debugging Dive: Sur40 Initialization Crash**
Alright, the debugger just hit a snag β it's pinpointing the crash in `sur40_init`, specifically at line 352. The code in question is this: `result = sur40_command(dev, SUR40_GET_VERSION, 0x00, buffer, 12);`. So, it looks like we're having an issue right in the initialization sequence when querying the device version. Okay, time to dive deeper into the root cause. My next step is clearly to go inspect the `sur40_command` function itself. Let's see what's happening there with the `SUR40_GET_VERSION` command and that buffer interaction. The problem must be happening in the command being executed. Perhaps a malformed command or bad buffer handling. Let's get to the bottom of this.
|
| 11/2 |
2026/08/09 09:35 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_command]
Results: map[SourceCode: 254: /* command wrapper */
255: static int sur40_command(struct sur40_state *dev,
256: u8 command, u16 index, void *buffer, u16 size)
257: {
258: return usb_control_msg(dev->usbdev, usb_rcvctrlpipe(dev->usbdev, 0),
259: command,
260: USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
261: 0x00, index, buffer, size, 1000);
262: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 12/2 |
2026/08/09 09:35 |
llm |
expert |
0m
Model:
Tokens:
input: 21403
output: 41
thoughts: 69
Thoughts: **Analyzing a Nasty Kernel Crash**
Okay, so I'm looking at this crash dump, and it's screaming "slab-use-after-free." The system's trying to read 8 bytes from this memory address, `ffff88807a69a000`, which is definitely a red flag. The object size is 4096 bytes, which I should be able to cross-reference later.
Hmm, but what's really grabbing my attention is that the access seems to involve `dev->usbdev`. This screams USB driver issue. My immediate thought is, what piece of code is referencing this object's memory? Let's take a look at `sur40_open`, which is likely where the problem is. I need to figure out where that memory is being freed or corrupted and when and where this access is occurring. Time to dive into the code...
|
| 13/2 |
2026/08/09 09:36 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_open]
Results: map[SourceCode: 391: /* Enable the device, polling will now start. */
392: static int sur40_open(struct input_dev *input)
393: {
394: struct sur40_state *sur40 = input_get_drvdata(input);
395:
396: dev_dbg(sur40->dev, "open\n");
397: return sur40_init(sur40);
398: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 14/2 |
2026/08/09 09:36 |
llm |
expert |
0m
Model:
Tokens:
input: 21872
output: 41
thoughts: 143
Thoughts: **Debugging a Touchscreen Driver**
Okay, so I'm digging into this bug report regarding the `sur40` touchscreen driver. It looks like the issue stems from a crash, specifically a `kfree` error. The report tells me that the error occurs when a task (task ID 5634) is trying to free some memory, and the backtrace points directly to the `sur40_probe` function. This makes sense; the bug description tells me that the problem shows up right after opening the input device, and `sur40_open` is called then.
So, the trace tells me `sur40_probe` is where the memory issue begins. That's my immediate focus. I need to understand what `sur40_probe` is doing with memory allocation and deallocation within this driver. I'll need to go through the code, particularly around line 816, where it's crashing according to the backtrace, and understand how it interacts with the USB subsystem using the `usb_probe_interface` call, as the report indicates. I'm guessing that there's either a double-free, a use-after-free, or perhaps a memory leak somewhere within the initialization or resource management of the device. This probe function is critical. Let's see what we can find.
|
| 15/2 |
2026/08/09 09:36 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_probe]
Results: map[SourceCode: 650: /* Check candidate USB interface. */
651: static int sur40_probe(struct usb_interface *interface,
652: const struct usb_device_id *id)
653: {
654: struct usb_device *usbdev = interface_to_usbdev(interface);
655: struct sur40_state *sur40;
656: struct usb_host_interface *iface_desc;
657: struct usb_endpoint_descriptor *endpoint;
658: struct input_dev *input;
659: int error;
660:
661: /* Check if we really have the right interface. */
662: iface_desc = interface->cur_altsetting;
663: if (iface_desc->desc.bInterfaceClass != 0xFF)
664: return -ENODEV;
665:
666: if (iface_desc->desc.bNumEndpoints < 5)
667: return -ENODEV;
668:
669: /* Use endpoint #4 (0x86). */
670: endpoint = &iface_desc->endpoint[4].desc;
671: if (endpoint->bEndpointAddress != TOUCH_ENDPOINT)
672: return -ENODEV;
673:
674: /* Allocate memory for our device state and initialize it. */
675: sur40 = kzalloc_obj(*sur40);
676: if (!sur40)
677: return -ENOMEM;
678:
679: input = input_allocate_device();
680: if (!input) {
681: error = -ENOMEM;
682: goto err_free_dev;
683: }
684:
685: /* initialize locks/lists */
686: INIT_LIST_HEAD(&sur40->buf_list);
687: spin_lock_init(&sur40->qlock);
688: mutex_init(&sur40->lock);
689:
690: /* Set up regular input device structure */
691: input->name = DRIVER_LONG;
692: usb_to_input_id(usbdev, &input->id);
693: usb_make_path(usbdev, sur40->phys, sizeof(sur40->phys));
694: strlcat(sur40->phys, "/input0", sizeof(sur40->phys));
695: input->phys = sur40->phys;
696: input->dev.parent = &interface->dev;
697:
698: input->open = sur40_open;
699: input->close = sur40_close;
700:
701: error = sur40_input_setup_events(input);
702: if (error)
703: goto err_free_input;
704:
705: input_set_drvdata(input, sur40);
706: error = input_setup_polling(input, sur40_poll);
707: if (error) {
708: dev_err(&interface->dev, "failed to set up polling");
709: goto err_free_input;
710: }
711:
712: input_set_poll_interval(input, POLL_INTERVAL);
713:
714: sur40->usbdev = usbdev;
715: sur40->dev = &interface->dev;
716: sur40->input = input;
717:
718: /* use the bulk-in endpoint tested above */
719: sur40->bulk_in_size = usb_endpoint_maxp(endpoint);
720: sur40->bulk_in_epaddr = endpoint->bEndpointAddress;
721: sur40->bulk_in_buffer = kmalloc(sur40->bulk_in_size, GFP_KERNEL);
722: if (!sur40->bulk_in_buffer) {
723: dev_err(&interface->dev, "Unable to allocate input buffer.");
724: error = -ENOMEM;
725: goto err_free_input;
726: }
727:
728: /* register the polled input device */
729: error = input_register_device(input);
730: if (error) {
731: dev_err(&interface->dev,
732: "Unable to register polled input device.");
733: goto err_free_buffer;
734: }
735:
736: /* register the video master device */
737: snprintf(sur40->v4l2.name, sizeof(sur40->v4l2.name), "%s", DRIVER_LONG);
738: error = v4l2_device_register(sur40->dev, &sur40->v4l2);
739: if (error) {
740: dev_err(&interface->dev,
741: "Unable to register video master device.");
742: goto err_unreg_v4l2;
743: }
744:
745: /* initialize the lock and subdevice */
746: sur40->queue = sur40_queue;
747: sur40->queue.drv_priv = sur40;
748: sur40->queue.lock = &sur40->lock;
749: sur40->queue.dev = sur40->dev;
750:
751: /* initialize the queue */
752: error = vb2_queue_init(&sur40->queue);
753: if (error)
754: goto err_unreg_v4l2;
755:
756: sur40->pix_fmt = sur40_pix_format[0];
757: sur40->vdev = sur40_video_device;
758: sur40->vdev.v4l2_dev = &sur40->v4l2;
759: sur40->vdev.lock = &sur40->lock;
760: sur40->vdev.queue = &sur40->queue;
761: video_set_drvdata(&sur40->vdev, sur40);
762:
763: /* initialize the control handler for 4 controls */
764: v4l2_ctrl_handler_init(&sur40->hdl, 4);
765: sur40->v4l2.ctrl_handler = &sur40->hdl;
766: sur40->vsvideo = (SUR40_CONTRAST_DEF << 4) | SUR40_GAIN_DEF;
767:
768: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_BRIGHTNESS,
769: SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX, 1, clamp(brightness,
770: (uint)SUR40_BRIGHTNESS_MIN, (uint)SUR40_BRIGHTNESS_MAX));
771:
772: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_CONTRAST,
773: SUR40_CONTRAST_MIN, SUR40_CONTRAST_MAX, 1, clamp(contrast,
774: (uint)SUR40_CONTRAST_MIN, (uint)SUR40_CONTRAST_MAX));
775:
776: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_GAIN,
777: SUR40_GAIN_MIN, SUR40_GAIN_MAX, 1, clamp(gain,
778: (uint)SUR40_GAIN_MIN, (uint)SUR40_GAIN_MAX));
779:
780: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops,
781: V4L2_CID_BACKLIGHT_COMPENSATION, SUR40_BACKLIGHT_MIN,
782: SUR40_BACKLIGHT_MAX, 1, SUR40_BACKLIGHT_DEF);
783:
784: v4l2_ctrl_handler_setup(&sur40->hdl);
785:
786: if (sur40->hdl.error) {
787: dev_err(&interface->dev,
788: "Unable to register video controls.");
789: v4l2_ctrl_handler_free(&sur40->hdl);
790: error = sur40->hdl.error;
791: goto err_unreg_v4l2;
792: }
793:
794: error = video_register_device(&sur40->vdev, VFL_TYPE_TOUCH, -1);
795: if (error) {
796: dev_err(&interface->dev,
797: "Unable to register video subdevice.");
798: goto err_unreg_video;
799: }
800:
801: /* we can register the device now, as it is ready */
802: usb_set_intfdata(interface, sur40);
803: dev_dbg(&interface->dev, "%s is now attached\n", DRIVER_DESC);
804:
805: return 0;
806:
807: err_unreg_video:
808: video_unregister_device(&sur40->vdev);
809: err_unreg_v4l2:
810: v4l2_device_unregister(&sur40->v4l2);
811: err_free_buffer:
812: kfree(sur40->bulk_in_buffer);
813: err_free_input:
814: input_free_device(input);
815: err_free_dev:
816: kfree(sur40);
817:
818: return error;
819: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 16/2 |
2026/08/09 09:36 |
llm |
expert |
0m
Model:
Tokens:
input: 25304
output: 36
thoughts: 469
Thoughts: **Debugging a Driver Probe Function**
Okay, let's break this down. I'm looking at the `sur40_probe` function and something feels off. Specifically, I'm concerned about the error handling sequence. It looks like `err_unreg_video` is called, and that calls `video_unregister_device(&sur40->vdev)`. But hold on, wasn't `input_register_device` called *before* this, way back at line 729?
My immediate thought is that if `video_register_device` fails (line 794), it jumps to `err_unreg_video`, which then *unregisters* the video device. However, if `video_register_device` fails, the device may not have even been registered yet. So, calling `video_unregister_device` in this specific case is redundant. Although, I recall that `video_unregister_device` has a check internally to see if it needs to do anything, so it might be a no-op if the registration failed. Regardless, `err_unreg_video` leads to other error routines, including `err_unreg_v4l2`, `err_free_buffer`, `err_free_input`, and finally `err_free_dev`.
Now, here's where it gets interesting: `err_free_input` calls `input_free_device(input)`. But wasn't `input_register_device(input)` called earlier? I'm pretty sure that if `input_register_device` *succeeds*, the correct function to call is `input_unregister_device(input)` to properly clean up the input device.
Wait a second, when `input_register_device` is successful, it transfers ownership of the `input` device to the input core. If that function succeeds, then `input_free_device` shouldn't be called directly. `input_unregister_device` should be responsible for freeing it.
Thinking about the implications, if `input_register_device` succeeds, the input device becomes accessible to userspace. They can open it, interact with it. However, if `sur40_probe` fails at, let's say, `video_register_device` or even `v4l2_ctrl_handler_setup`, the code then jumps into the error handler and eventually `kfree(sur40)` is called at `err_free_dev`. The problem is, the input device would still be *registered*, since I don't see `input_unregister_device` being called anywhere in the error path!
I'm starting to see a resource leak. Specifically, the device is registered to the input subsystem but then not properly unregistered, even when the probe function fails. This could lead to a dangling pointer to a freed device, or some other corruption issue. `err_free_input` calls `input_free_device(input)`, which seems like a direct call to free the input device, but I think that is wrong, and `input_unregister_device` should be called instead. Now, I need to check the definition of `input_free_device` to confirm what I'm thinking, but I'm pretty sure this is a bug.
|
| 17/2 |
2026/08/09 09:36 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/input.c Name:input_free_device]
Results: map[SourceCode:2012: /**
2013: * input_free_device - free memory occupied by input_dev structure
2014: * @dev: input device to free
2015: *
2016: * This function should only be used if input_register_device()
2017: * was not called yet or if it failed. Once device was registered
2018: * use input_unregister_device() and memory will be freed once last
2019: * reference to the device is dropped.
2020: *
2021: * Device should be allocated by input_allocate_device().
2022: *
2023: * NOTE: If there are references to the input device then memory
2024: * will not be freed until last reference is dropped.
2025: */
2026: void input_free_device(struct input_dev *dev)
2027: {
2028: if (dev) {
2029: if (dev->devres_managed)
2030: WARN_ON(devres_destroy(dev->dev.parent,
2031: devm_input_device_release,
2032: devm_input_device_match,
2033: dev));
2034: input_put_device(dev);
2035: }
2036: }
SourceFile:drivers/input/input.c]
|
| 18/2 |
2026/08/09 09:36 |
llm |
expert |
0m
Model:
Tokens:
input: 26906
output: 41
thoughts: 867
Thoughts: **Use-After-Free Vulnerability in Input Device Handling**
Okay, so here's what's happening. I see that `input_free_device` is called, and my initial thought was that it completely frees the device. But that's not quite right. It only *drops* a reference to the device. The actual device is still registered with the input core, meaning it's still visible to and accessible by userspace.
If `input_register_device` succeeds, it essentially takes ownership and registers it. If `input_free_device` then gets called, we release the initial reference obtained from `input_allocate_device`, but the device is *still* registered. So userspace can still open the device.
When userspace tries to open the device (e.g., via `open()`), the `sur40_open` function gets called. Now, `sur40_open` uses `input_get_drvdata(input)` to get the `sur40_state` struct. Here's the kicker: `sur40` was previously freed using `kfree(sur40)` in `err_free_dev`. That's where the problem lies.
Let's drill down and confirm the error path within `sur40_probe`. I'm going through the code, and I see this error flow: If `v4l2_device_register` fails, execution goes to `err_unreg_v4l2`. `v4l2_device_unregister(&sur40->v4l2)` is called, followed by `kfree(sur40->bulk_in_buffer)` in `err_free_buffer`, then `input_free_device(input)` is called in `err_free_input`, and lastly, `kfree(sur40)` is called.
The key observation is that `input_register_device` succeeded at the beginning. So, while `input_free_device` drops the initial reference, the input core still *has* a reference because it's registered. And after that `sur40` is freed.
Now, `sur40_probe` returns an error. Later, when the device is accessed by userspace, like through a udev event, `sur40_open` gets called, and it tries to access the freed `sur40` struct.
This is a classic use-after-free vulnerability! The root cause? The missing call to `input_unregister_device` on the error path to properly unregister.
Now I need to assess how exploitable this is. The freed object is `sur40`. Looks like `sur40` is allocated with `kzalloc`, which should give a known size. Is this `sizeof(struct sur40_state)`? It should be around 4096.
|
| 19/2 |
2026/08/09 09:37 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_state]
Results: map[SourceCode: 204: /* master device state */
205: struct sur40_state {
206:
207: struct usb_device *usbdev;
208: struct device *dev;
209: struct input_dev *input;
210:
211: struct v4l2_device v4l2;
212: struct video_device vdev;
213: struct mutex lock;
214: struct v4l2_pix_format pix_fmt;
215: struct v4l2_ctrl_handler hdl;
216:
217: struct vb2_queue queue;
218: struct list_head buf_list;
219: spinlock_t qlock;
220: int sequence;
221:
222: struct sur40_data *bulk_in_buffer;
223: size_t bulk_in_size;
224: u8 bulk_in_epaddr;
225: u8 vsvideo;
226:
227: char phys[64];
228: };
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 20/2 |
2026/08/09 09:37 |
llm |
expert |
0m
Model:
Tokens:
input: 28788
output: 41
thoughts: 284
Thoughts: **Analyzing the `sur40_state` Structure Size**
Okay, hold on a second. Something doesn't seem right. The `sizeof(struct sur40_state)` is *not* what I expected. It's supposed to be 4096, but I'm pretty sure that can't be right after a quick glance. Let's break this down systematically.
First, let's go through the members: I see `struct usb_device *usbdev` (8 bytes - a pointer), then `struct device *dev` (another 8 bytes - another pointer), and `struct input_dev *input` (again, an 8-byte pointer). Alright, so far, so good, they're accumulating.
Next, we have `struct v4l2_device v4l2` and `struct video_device vdev`. These can vary, of course, depending on the kernel version and driver implementation, but those will likely be a few hundred bytes each, at most. Let's be generous.
Then we have `struct mutex lock` at 32 bytes, right? And `struct v4l2_pix_format pix_fmt` at 48. Then, `struct v4l2_ctrl_handler hdl`... maybe around 100 bytes is a fair estimate for that.
Moving on, `struct vb2_queue queue` - probably around 500 bytes. Seems reasonable. Then we have `struct list_head buf_list` (16 bytes), a `spinlock_t qlock` (4 bytes), and an `int sequence` (4 bytes).
And there's a pointer, `struct sur40_data *bulk_in_buffer` (8 bytes), as well as a `size_t bulk_in_size` (8 bytes) . Plus, we have an `u8 bulk_in_epaddr` and `u8 vsvideo` (1 byte each). Finally, there's `char phys[64]` (64 bytes).
So, now let's get the exact size of `struct sur40_state`. This is clearly going to be a lot less than the allocated 4K.
|
| 21/2 |
2026/08/09 09:37 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_state]
Results: map[Fields:[map[Name:usbdev OffsetBits:0 SizeBits:64] map[Name:dev OffsetBits:64 SizeBits:64] map[Name:input OffsetBits:128 SizeBits:64] map[Name:v4l2 OffsetBits:192 SizeBits:1472] map[Name:vdev OffsetBits:1664 SizeBits:13568] map[Name:lock OffsetBits:15232 SizeBits:1088] map[Name:pix_fmt OffsetBits:16320 SizeBits:384] map[Name:hdl OffsetBits:16704 SizeBits:2496] map[Name:queue OffsetBits:19200 SizeBits:4608] map[Name:buf_list OffsetBits:23808 SizeBits:128] map[Name:qlock OffsetBits:23936 SizeBits:512] map[Name:sequence OffsetBits:24448 SizeBits:32] map[Name:bulk_in_buffer OffsetBits:24512 SizeBits:64] map[Name:bulk_in_size OffsetBits:24576 SizeBits:64] map[Name:bulk_in_epaddr OffsetBits:24640 SizeBits:8] map[Name:vsvideo OffsetBits:24648 SizeBits:8] map[Name:phys OffsetBits:24656 SizeBits:512]]]
|
| 22/2 |
2026/08/09 09:37 |
llm |
expert |
0m
Model:
Tokens:
input: 29992
output: 41
thoughts: 868
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability**
Okay, let's break this down. The target memory allocation is 24656 + 512 = 25168 bits, which is 3146 bytes. That puts it neatly within the `kmalloc-4k` size class. The critical point is that this is a use-after-free vulnerability, which is always interesting.
The sequence starts in `sur40_open`. It retrieves driver-specific data (`sur40_state`) using `input_get_drvdata` and calls `sur40_init`. Within `sur40_init`, a buffer of 24 bytes is allocated using `kmalloc`. Then a command (`SUR40_GET_VERSION`) is sent to the USB device via `sur40_command`. The core of the problem lies within `sur40_command` and the call to `usb_control_msg`. It dereferences `dev->usbdev`, and *that's* the vulnerable part. Specifically, `usb_control_msg` takes a `struct usb_device *dev` as its first argument. If an attacker can control what `sur40->usbdev` points to, we can control the first argument to `usb_control_msg`.
Initially, I'd expect crashing if the pointer is completely controlled. But `usb_control_msg` is quite complex, so we might be able to achieve something more interesting than just a crash. I need to delve into the code of `usb_control_msg` and understand which fields it dereferences from the passed `dev` structure.
The important point is that `sur40_open` is called when the input device is opened. And that's usually accessible to the "input" group or root. However, the report mentions that `udevd` is involved. It runs as root, and it automatically probes new input devices to determine things like `ID_INPUT_*` properties.
This means the UAF is triggered automatically when the device is registered! Thatβs *fantastic* from an exploitability perspective β a highly reliable trigger.
Can an unprivileged user exploit this? The vulnerability is triggered when a malicious USB device is plugged in. The USB device *must* pass the initial checks in `sur40_probe`. These are based on interface class, number of endpoints, and endpoint address:
* `iface_desc->desc.bInterfaceClass != 0xFF`
* `iface_desc->desc.bNumEndpoints < 5`
* `endpoint->bEndpointAddress != TOUCH_ENDPOINT`
Then, it needs to fail at `v4l2_device_register` or later. `v4l2_device_register` itself doesn't often fail unless `dev_get_drvdata` is already set, which shouldn't be the case here. `vb2_queue_init` might fail if `q->io_modes` is zero. However, `q->io_modes` is initialized in `sur40_queue`? Letβs check `sur40_queue` to ensure the scenario is well defined.
|
| 23/2 |
2026/08/09 09:37 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_queue]
Results: map[SourceCode:1112: static const struct vb2_queue sur40_queue = {
1113: .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
1114: /*
1115: * VB2_USERPTR in currently not enabled: passing a user pointer to
1116: * dma-sg will result in segment sizes that are not a multiple of
1117: * 512 bytes, which is required by the host controller.
1118: */
1119: .io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF,
1120: .buf_struct_size = sizeof(struct sur40_buffer),
1121: .ops = &sur40_queue_ops,
1122: .mem_ops = &vb2_dma_sg_memops,
1123: .timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC,
1124: .min_queued_buffers = 3,
1125: };
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 24/2 |
2026/08/09 09:37 |
llm |
expert |
0m
Model:
Tokens:
input: 31905
output: 41
thoughts: 725
Thoughts: **Analyzing the Failure in `sur40_probe`**
Okay, so I'm digging into this `sur40_probe` issue. My initial thought is to look for potential memory allocation failures β `vb2_queue_init` is a clear candidate. I need to also consider `v4l2_ctrl_handler_setup` and `video_register_device`. What if there's some kind of race condition or device state issue? An attacker unplugging the USB device during `sur40_probe` could potentially cause a problem, as could simply exhausting available minor numbers through a flood of video device registrations, forcing the `video_register_device` to fail.
The syzkaller reproducer doesn't seem to be doing anything overtly complex to trigger this, which makes this more interesting and suggests a deeper underlying issue. I've looked at the reproducer and it uses `syz_usb_connect` to simulate a USB device connection, and it provides a specific USB descriptor.
Now, let me examine the USB descriptor. It looks like it defines a device with a standard device descriptor, a configuration descriptor, and an interface descriptor, followed by five endpoints: `0x81`, `0x82`, `0x83`, `0x84`, and `0x86`. All are bulk IN endpoints with a `wMaxPacketSize` of `0x0200`. Seems pretty standard for what it is emulating, but there must be something in the setup that is causing `sur40_probe` to fail.
I am going to check `sur40_probe` again. Let's trace it and see where the actual failure happens.
|
| 25/2 |
2026/08/09 09:37 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_probe]
Results: map[SourceCode: 650: /* Check candidate USB interface. */
651: static int sur40_probe(struct usb_interface *interface,
652: const struct usb_device_id *id)
653: {
654: struct usb_device *usbdev = interface_to_usbdev(interface);
655: struct sur40_state *sur40;
656: struct usb_host_interface *iface_desc;
657: struct usb_endpoint_descriptor *endpoint;
658: struct input_dev *input;
659: int error;
660:
661: /* Check if we really have the right interface. */
662: iface_desc = interface->cur_altsetting;
663: if (iface_desc->desc.bInterfaceClass != 0xFF)
664: return -ENODEV;
665:
666: if (iface_desc->desc.bNumEndpoints < 5)
667: return -ENODEV;
668:
669: /* Use endpoint #4 (0x86). */
670: endpoint = &iface_desc->endpoint[4].desc;
671: if (endpoint->bEndpointAddress != TOUCH_ENDPOINT)
672: return -ENODEV;
673:
674: /* Allocate memory for our device state and initialize it. */
675: sur40 = kzalloc_obj(*sur40);
676: if (!sur40)
677: return -ENOMEM;
678:
679: input = input_allocate_device();
680: if (!input) {
681: error = -ENOMEM;
682: goto err_free_dev;
683: }
684:
685: /* initialize locks/lists */
686: INIT_LIST_HEAD(&sur40->buf_list);
687: spin_lock_init(&sur40->qlock);
688: mutex_init(&sur40->lock);
689:
690: /* Set up regular input device structure */
691: input->name = DRIVER_LONG;
692: usb_to_input_id(usbdev, &input->id);
693: usb_make_path(usbdev, sur40->phys, sizeof(sur40->phys));
694: strlcat(sur40->phys, "/input0", sizeof(sur40->phys));
695: input->phys = sur40->phys;
696: input->dev.parent = &interface->dev;
697:
698: input->open = sur40_open;
699: input->close = sur40_close;
700:
701: error = sur40_input_setup_events(input);
702: if (error)
703: goto err_free_input;
704:
705: input_set_drvdata(input, sur40);
706: error = input_setup_polling(input, sur40_poll);
707: if (error) {
708: dev_err(&interface->dev, "failed to set up polling");
709: goto err_free_input;
710: }
711:
712: input_set_poll_interval(input, POLL_INTERVAL);
713:
714: sur40->usbdev = usbdev;
715: sur40->dev = &interface->dev;
716: sur40->input = input;
717:
718: /* use the bulk-in endpoint tested above */
719: sur40->bulk_in_size = usb_endpoint_maxp(endpoint);
720: sur40->bulk_in_epaddr = endpoint->bEndpointAddress;
721: sur40->bulk_in_buffer = kmalloc(sur40->bulk_in_size, GFP_KERNEL);
722: if (!sur40->bulk_in_buffer) {
723: dev_err(&interface->dev, "Unable to allocate input buffer.");
724: error = -ENOMEM;
725: goto err_free_input;
726: }
727:
728: /* register the polled input device */
729: error = input_register_device(input);
730: if (error) {
731: dev_err(&interface->dev,
732: "Unable to register polled input device.");
733: goto err_free_buffer;
734: }
735:
736: /* register the video master device */
737: snprintf(sur40->v4l2.name, sizeof(sur40->v4l2.name), "%s", DRIVER_LONG);
738: error = v4l2_device_register(sur40->dev, &sur40->v4l2);
739: if (error) {
740: dev_err(&interface->dev,
741: "Unable to register video master device.");
742: goto err_unreg_v4l2;
743: }
744:
745: /* initialize the lock and subdevice */
746: sur40->queue = sur40_queue;
747: sur40->queue.drv_priv = sur40;
748: sur40->queue.lock = &sur40->lock;
749: sur40->queue.dev = sur40->dev;
750:
751: /* initialize the queue */
752: error = vb2_queue_init(&sur40->queue);
753: if (error)
754: goto err_unreg_v4l2;
755:
756: sur40->pix_fmt = sur40_pix_format[0];
757: sur40->vdev = sur40_video_device;
758: sur40->vdev.v4l2_dev = &sur40->v4l2;
759: sur40->vdev.lock = &sur40->lock;
760: sur40->vdev.queue = &sur40->queue;
761: video_set_drvdata(&sur40->vdev, sur40);
762:
763: /* initialize the control handler for 4 controls */
764: v4l2_ctrl_handler_init(&sur40->hdl, 4);
765: sur40->v4l2.ctrl_handler = &sur40->hdl;
766: sur40->vsvideo = (SUR40_CONTRAST_DEF << 4) | SUR40_GAIN_DEF;
767:
768: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_BRIGHTNESS,
769: SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX, 1, clamp(brightness,
770: (uint)SUR40_BRIGHTNESS_MIN, (uint)SUR40_BRIGHTNESS_MAX));
771:
772: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_CONTRAST,
773: SUR40_CONTRAST_MIN, SUR40_CONTRAST_MAX, 1, clamp(contrast,
774: (uint)SUR40_CONTRAST_MIN, (uint)SUR40_CONTRAST_MAX));
775:
776: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_GAIN,
777: SUR40_GAIN_MIN, SUR40_GAIN_MAX, 1, clamp(gain,
778: (uint)SUR40_GAIN_MIN, (uint)SUR40_GAIN_MAX));
779:
780: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops,
781: V4L2_CID_BACKLIGHT_COMPENSATION, SUR40_BACKLIGHT_MIN,
782: SUR40_BACKLIGHT_MAX, 1, SUR40_BACKLIGHT_DEF);
783:
784: v4l2_ctrl_handler_setup(&sur40->hdl);
785:
786: if (sur40->hdl.error) {
787: dev_err(&interface->dev,
788: "Unable to register video controls.");
789: v4l2_ctrl_handler_free(&sur40->hdl);
790: error = sur40->hdl.error;
791: goto err_unreg_v4l2;
792: }
793:
794: error = video_register_device(&sur40->vdev, VFL_TYPE_TOUCH, -1);
795: if (error) {
796: dev_err(&interface->dev,
797: "Unable to register video subdevice.");
798: goto err_unreg_video;
799: }
800:
801: /* we can register the device now, as it is ready */
802: usb_set_intfdata(interface, sur40);
803: dev_dbg(&interface->dev, "%s is now attached\n", DRIVER_DESC);
804:
805: return 0;
806:
807: err_unreg_video:
808: video_unregister_device(&sur40->vdev);
809: err_unreg_v4l2:
810: v4l2_device_unregister(&sur40->v4l2);
811: err_free_buffer:
812: kfree(sur40->bulk_in_buffer);
813: err_free_input:
814: input_free_device(input);
815: err_free_dev:
816: kfree(sur40);
817:
818: return error;
819: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 26/2 |
2026/08/09 09:37 |
llm |
expert |
0m
Model:
Tokens:
input: 35999
output: 51
thoughts: 771
Thoughts: **Analyzing Potential Failure Points in V4L2 Device Registration**
Okay, so I'm looking at this `video_register_device` function and it strikes me that it *could* fail if there are no more free minor numbers for `VFL_TYPE_TOUCH`. That's `VFL_TYPE_TOUCH` which maps to `/dev/v4l-touchX`, and we have a minor range of 0-255. An attacker, theoretically, could create a bunch of dummy video devices and exhaust those minors, causing `video_register_device` to fail.
But, hold on. What about `v4l2_ctrl_handler_setup`? Nope, that's just setting up controls; it's pretty reliable. And `vb2_queue_init`? Hmm, that can fail if `q->io_modes` is messed up, but that value is hardcoded, so that's probably not the culprit.
Now, what about `v4l2_device_register`? *That* could fail. It checks if `dev_set_drvdata` fails, but that's a rare failure case. Or if `device_register` fails, maybe due to an out-of-memory condition.
Wait a second, the reproducer from Syzkaller *doesn't* seem to be doing anything to exhaust minors or memory. It just connects a USB device. This is weird! Why did the original reproducer fail, then? We need to look at this more closely.
Let me take another look at the Syzkaller reproducer:
```c
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000, /*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=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
memcpy((void*)0x200000000100, "... [truncated large byte array] ...", 71);
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x47, /*dev=*/0x200000000100, /*conn_descs=*/0);
```
Okay, it's just doing some `mmap` calls, writes a bit of text, and then calls `syz_usb_connect`.
Aha! Looking at `sur40_probe`, it calls `v4l2_device_register(sur40->dev, &sur40->v4l2)`. And `sur40->dev` is `&interface->dev`. So, if `v4l2_device_register` fails *that* returns an error. The question now is: Why would `v4l2_device_register` fail *in this context*? I need to go back and examine `v4l2_device_register` itself.
|
| 27/2 |
2026/08/09 09:37 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/v4l2-core/v4l2-device.c Name:v4l2_device_register]
Results: map[SourceCode: 17: int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
18: {
19: if (v4l2_dev == NULL)
20: return -EINVAL;
21:
22: INIT_LIST_HEAD(&v4l2_dev->subdevs);
23: spin_lock_init(&v4l2_dev->lock);
24: v4l2_prio_init(&v4l2_dev->prio);
25: kref_init(&v4l2_dev->ref);
26: get_device(dev);
27: v4l2_dev->dev = dev;
28: if (dev == NULL) {
29: /* If dev == NULL, then name must be filled in by the caller */
30: if (WARN_ON(!v4l2_dev->name[0]))
31: return -EINVAL;
32: return 0;
33: }
34:
35: /* Set name to driver name + device name if it is empty. */
36: if (!v4l2_dev->name[0])
37: snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
38: dev->driver->name, dev_name(dev));
39: if (!dev_get_drvdata(dev))
40: dev_set_drvdata(dev, v4l2_dev);
41: return 0;
42: }
SourceFile:drivers/media/v4l2-core/v4l2-device.c]
|
| 28/2 |
2026/08/09 09:37 |
llm |
expert |
0m
Model:
Tokens:
input: 38210
output: 47
thoughts: 34
Thoughts: Here's my take on the situation:
**Initial Assessment: A Baffling `v4l2_device_register`**
Okay, hold on a second. `v4l2_device_register`... that function's almost bulletproof. In the vast majority of my experience, it *never* returns an error. Itβs pretty much designed to succeed and simply returns 0 upon success. So, the fact that there's an issue reported here is immediately raising a flag. This is unusual behavior and demands closer scrutiny. What's causing this seemingly abnormal outcome? Perhaps something fundamental is amiss. What is going on upstream to cause this result?
Now, let me shift gears and consider the related functions. Okay, what about the initialization function? `vb2_queue_init`? I'd better revisit the status of that function now. This is a very similar function to the first. Let's see what is going on there.
|
| 29/2 |
2026/08/09 09:38 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/common/videobuf2/videobuf2-core.c Name:vb2_queue_init]
Results: map[SourceCode: 948: int vb2_queue_init(struct vb2_queue *q)
949: {
950: return vb2_queue_init_name(q, NULL);
951: }
SourceFile:drivers/media/common/videobuf2/videobuf2-v4l2.c]
|
| 30/2 |
2026/08/09 09:38 |
llm |
expert |
0m
Model:
Tokens:
input: 38597
output: 49
thoughts: 14
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Analyzing `vb2_queue_init_name`**
Alright, let's dive into this `vb2_queue_init_name` function. My initial thought is to quickly ascertain its purpose within the broader Video4Linux (V4L) context. Based on the name, I'm expecting it to be responsible for initializing the name or perhaps some naming-related attributes of a `vb2_queue` structure. This likely involves setting a default name or generating a unique name, crucial for device file creation or internal bookkeeping within the V4L subsystem, especially when dealing with multiple streams or devices. I'll need to check the function's definition and usage within the kernel to fully understand its role in memory management, driver integration and the typical data structures that are handled within this context. I'll be particularly interested in whether it handles any error conditions, and how those are propagated. The underlying considerations of concurrency and race conditions are immediately relevant to any initialization function, so these are considerations for what might be present, and also for its implications on overall system stability.
|
| 31/2 |
2026/08/09 09:38 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/common/videobuf2/videobuf2-core.c Name:vb2_queue_init_name]
Results: map[SourceCode: 904: int vb2_queue_init_name(struct vb2_queue *q, const char *name)
905: {
906: /* vb2_memory should match with v4l2_memory */
907: BUILD_BUG_ON(VB2_MEMORY_MMAP != (int)V4L2_MEMORY_MMAP);
908: BUILD_BUG_ON(VB2_MEMORY_USERPTR != (int)V4L2_MEMORY_USERPTR);
909: BUILD_BUG_ON(VB2_MEMORY_DMABUF != (int)V4L2_MEMORY_DMABUF);
910:
911: /*
912: * Sanity check
913: */
914: if (WARN_ON(!q) ||
915: WARN_ON(q->timestamp_flags &
916: ~(V4L2_BUF_FLAG_TIMESTAMP_MASK |
917: V4L2_BUF_FLAG_TSTAMP_SRC_MASK)))
918: return -EINVAL;
919:
920: /* Warn that the driver should choose an appropriate timestamp type */
921: WARN_ON((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
922: V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN);
923:
924: if (q->buf_struct_size == 0)
925: q->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
926:
927: q->buf_ops = &v4l2_buf_ops;
928: q->is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(q->type);
929: q->is_output = V4L2_TYPE_IS_OUTPUT(q->type);
930: q->copy_timestamp = (q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK)
931: == V4L2_BUF_FLAG_TIMESTAMP_COPY;
932: /*
933: * For compatibility with vb1: if QBUF hasn't been called yet, then
934: * return EPOLLERR as well. This only affects capture queues, output
935: * queues will always initialize waiting_for_buffers to false.
936: */
937: q->quirk_poll_must_check_waiting_for_buffers = true;
938:
939: if (name)
940: strscpy(q->name, name, sizeof(q->name));
941: else
942: q->name[0] = '\0';
943:
944: return vb2_core_queue_init(q);
945: }
SourceFile:drivers/media/common/videobuf2/videobuf2-v4l2.c]
|
| 32/2 |
2026/08/09 09:38 |
llm |
expert |
0m
Model:
Tokens:
input: 39720
output: 49
thoughts: 14
Thoughts: Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:
**Analyzing `vb2_core_queue_init`**
Alright, time to get to it. First, I need to check `vb2_core_queue_init`. I'm going to quickly refresh my memory of the relevant structures and definitions, ensuring I have the context I need. I'll need to understand what this function is designed to do within the larger system. Is it responsible for initialization, configuration, or perhaps something else entirely? Based on the name it seems like it sets up a queue. I'll focus on the data structures it interacts with and the critical steps involved in ensuring the queue is properly prepared for operation. I need to be able to accurately explain the role of this function. I'll pay close attention to the parameters, as these inputs will undoubtedly provide insight into its behavior and dependencies. I will make a point to check how error conditions are handled.
|
| 33/2 |
2026/08/09 09:38 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/common/videobuf2/videobuf2-core.c Name:vb2_core_queue_init]
Results: map[SourceCode:2590: int vb2_core_queue_init(struct vb2_queue *q)
2591: {
2592: /*
2593: * Sanity check
2594: */
2595: /*
2596: * For drivers who don't support max_num_buffers ensure
2597: * a backward compatibility.
2598: */
2599: if (!q->max_num_buffers)
2600: q->max_num_buffers = VB2_MAX_FRAME;
2601:
2602: /* The maximum is limited by offset cookie encoding pattern */
2603: q->max_num_buffers = min_t(unsigned int, q->max_num_buffers, MAX_BUFFER_INDEX);
2604:
2605: if (WARN_ON(!q) ||
2606: WARN_ON(!q->ops) ||
2607: WARN_ON(!q->mem_ops) ||
2608: WARN_ON(!q->type) ||
2609: WARN_ON(!q->io_modes) ||
2610: WARN_ON(!q->ops->queue_setup) ||
2611: WARN_ON(!q->ops->buf_queue))
2612: return -EINVAL;
2613:
2614: if (WARN_ON(q->max_num_buffers < VB2_MAX_FRAME) ||
2615: WARN_ON(q->min_queued_buffers > q->max_num_buffers))
2616: return -EINVAL;
2617:
2618: if (WARN_ON(q->requires_requests && !q->supports_requests))
2619: return -EINVAL;
2620:
2621: /*
2622: * This combination is not allowed since a non-zero value of
2623: * q->min_queued_buffers can cause vb2_core_qbuf() to fail if
2624: * it has to call start_streaming(), and the Request API expects
2625: * that queueing a request (and thus queueing a buffer contained
2626: * in that request) will always succeed. There is no method of
2627: * propagating an error back to userspace.
2628: */
2629: if (WARN_ON(q->supports_requests && q->min_queued_buffers))
2630: return -EINVAL;
2631:
2632: /*
2633: * If the driver needs 'min_queued_buffers' in the queue before
2634: * calling start_streaming() then the minimum requirement is
2635: * 'min_queued_buffers + 1' to keep at least one buffer available
2636: * for userspace.
2637: */
2638: if (q->min_reqbufs_allocation < q->min_queued_buffers + 1)
2639: q->min_reqbufs_allocation = q->min_queued_buffers + 1;
2640:
2641: if (WARN_ON(q->min_reqbufs_allocation > q->max_num_buffers))
2642: return -EINVAL;
2643:
2644: /* Warn if q->lock is NULL */
2645: if (WARN_ON(!q->lock))
2646: return -EINVAL;
2647:
2648: INIT_LIST_HEAD(&q->queued_list);
2649: INIT_LIST_HEAD(&q->done_list);
2650: spin_lock_init(&q->done_lock);
2651: mutex_init(&q->mmap_lock);
2652: init_waitqueue_head(&q->done_wq);
2653:
2654: q->memory = VB2_MEMORY_UNKNOWN;
2655:
2656: if (q->buf_struct_size == 0)
2657: q->buf_struct_size = sizeof(struct vb2_buffer);
2658:
2659: if (q->bidirectional)
2660: q->dma_dir = DMA_BIDIRECTIONAL;
2661: else
2662: q->dma_dir = q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
2663:
2664: if (q->name[0] == '\0')
2665: snprintf(q->name, sizeof(q->name), "%s-%p",
2666: q->is_output ? "out" : "cap", q);
2667:
2668: return 0;
2669: }
SourceFile:drivers/media/common/videobuf2/videobuf2-core.c]
|
| 34/2 |
2026/08/09 09:38 |
llm |
expert |
0m
Model:
Tokens:
input: 41329
output: 56
thoughts: 55
Thoughts: Okay, let me think this through.
**Analyzing the Initialization Flow**
Hang on a second... I was just assuming `vb2_core_queue_init` was the point of failure for memory allocation, but that's not right. It just returns a status, likely 0 on success. So that likely isn't the root of the issue here. I need to re-evaluate where initialization is likely failing. Alright, then `vb2_queue_init` isn't likely the culprit either. What *does* require resource setup? Let me re-think the initialization steps. What about `v4l2_ctrl_handler_setup`? I'd better double-check that one.
|
| 35/2 |
2026/08/09 09:38 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/v4l2-core/v4l2-ctrls-core.c Name:v4l2_ctrl_handler_setup]
Results: map[SourceCode:2738: int v4l2_ctrl_handler_setup(struct v4l2_ctrl_handler *hdl)
2739: {
2740: int ret;
2741:
2742: if (hdl == NULL)
2743: return 0;
2744:
2745: mutex_lock(hdl->lock);
2746: ret = __v4l2_ctrl_handler_setup(hdl);
2747: mutex_unlock(hdl->lock);
2748:
2749: return ret;
2750: }
SourceFile:drivers/media/v4l2-core/v4l2-ctrls-core.c]
|
| 36/2 |
2026/08/09 09:38 |
llm |
expert |
1m
Model:
Tokens:
input: 41805
output: 48
thoughts: 310
Thoughts: **Analyzing Potential Error Points in `sur40_probe`**
Okay, let's break this down. I'm looking at the `sur40_probe` function, and it seems like the control handler setup is where things could get tricky. Specifically, this section:
```c
v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_BRIGHTNESS,
SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX, 1, clamp(brightness,
(uint)SUR40_BRIGHTNESS_MIN, (uint)SUR40_BRIGHTNESS_MAX));
...
v4l2_ctrl_handler_setup(&sur40->hdl);
if (sur40->hdl.error) {
dev_err(&interface->dev,
"Unable to register video controls.");
v4l2_ctrl_handler_free(&sur40->hdl);
error = sur40->hdl.error;
goto err_unreg_v4l2;
}
```
The key is that `v4l2_ctrl_new_std` can set `sur40->hdl.error`. I know this can happen because it allocates memory, specifically using `kzalloc`. So, a classic out-of-memory scenario, especially under memory pressure, could trigger this `error` condition. That makes sense, and I have to note that syzkaller doesn't seem to have fault injection here, so it may be tricky to reproduce.
I have to wonder, though, are there other potential failure points? I wonder if there are any issues with `video_register_device`? I should examine that part of the code path to be sure.
|
| 37/2 |
2026/08/09 09:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/v4l2-core/v4l2-dev.c Name:video_register_device]
Results: map[SourceCode: 369: /**
370: * video_register_device - register video4linux devices
371: *
372: * @vdev: struct video_device to register
373: * @type: type of device to register, as defined by &enum vfl_devnode_type
374: * @nr: which device node number is desired:
375: * (0 == /dev/video0, 1 == /dev/video1, ..., -1 == first free)
376: *
377: * Internally, it calls __video_register_device(). Please see its
378: * documentation for more details.
379: *
380: * .. note::
381: * if video_register_device fails, the release() callback of
382: * &struct video_device structure is *not* called, so the caller
383: * is responsible for freeing any data. Usually that means that
384: * you video_device_release() should be called on failure.
385: */
386: static inline int __must_check video_register_device(struct video_device *vdev,
387: enum vfl_devnode_type type,
388: int nr)
389: {
390: return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
391: }
SourceFile:include/media/v4l2-dev.h]
|
| 38/2 |
2026/08/09 09:40 |
llm |
expert |
0m
Model:
Tokens:
input: 43011
output: 49
thoughts: 12
Thoughts: Here's my take on those thoughts, keeping in mind my expertise:
**Diving into the Video Device Registration**
Alright, let's get right to it. My immediate thought is, "Let's zero in on `__video_register_device`." Given my understanding, this is the core function for registering a new video device within the kernel's video subsystem, and I need to see exactly what's going on under the hood here. I'm thinking about the data structures being used, the locking mechanisms employed, and potential error paths. Is there any obvious place a bug could be hiding? I need to analyze this function.
|
| 39/2 |
2026/08/09 09:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/v4l2-core/v4l2-dev.c Name:__video_register_device]
Results: map[SourceCode: 910: int __video_register_device(struct video_device *vdev,
911: enum vfl_devnode_type type,
912: int nr, int warn_if_nr_in_use,
913: struct module *owner)
914: {
915: int i = 0;
916: int ret;
917: int minor_offset = 0;
918: int minor_cnt = VIDEO_NUM_DEVICES;
919: const char *name_base;
920:
921: /* A minor value of -1 marks this video device as never
922: having been registered */
923: vdev->minor = -1;
924:
925: /* the release callback MUST be present */
926: if (WARN_ON(!vdev->release))
927: return -EINVAL;
928: /* the v4l2_dev pointer MUST be present */
929: if (WARN_ON(!vdev->v4l2_dev))
930: return -EINVAL;
931: /* the device_caps field MUST be set for all but subdevs */
932: if (WARN_ON(type != VFL_TYPE_SUBDEV && !vdev->device_caps))
933: return -EINVAL;
934: /* the open and release file operations are mandatory */
935: if (WARN_ON(!vdev->fops || !vdev->fops->open || !vdev->fops->release))
936: return -EINVAL;
937:
938: /* v4l2_fh support */
939: spin_lock_init(&vdev->fh_lock);
940: INIT_LIST_HEAD(&vdev->fh_list);
941:
942: /* Part 1: check device type */
943: switch (type) {
944: case VFL_TYPE_VIDEO:
945: name_base = "video";
946: break;
947: case VFL_TYPE_VBI:
948: name_base = "vbi";
949: break;
950: case VFL_TYPE_RADIO:
951: name_base = "radio";
952: break;
953: case VFL_TYPE_SUBDEV:
954: name_base = "v4l-subdev";
955: break;
956: case VFL_TYPE_SDR:
957: /* Use device name 'swradio' because 'sdr' was already taken. */
958: name_base = "swradio";
959: break;
960: case VFL_TYPE_TOUCH:
961: name_base = "v4l-touch";
962: break;
963: default:
964: pr_err("%s called with unknown type: %d\n",
965: __func__, type);
966: return -EINVAL;
967: }
968:
969: vdev->vfl_type = type;
970: vdev->cdev = NULL;
971: if (vdev->dev_parent == NULL)
972: vdev->dev_parent = vdev->v4l2_dev->dev;
973: if (vdev->ctrl_handler == NULL)
974: vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
975: /* If the prio state pointer is NULL, then use the v4l2_device
976: prio state. */
977: if (vdev->prio == NULL)
978: vdev->prio = &vdev->v4l2_dev->prio;
979:
980: /* Part 2: find a free minor, device node number and device index. */
981: #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
982: /* Keep the ranges for the first four types for historical
983: * reasons.
984: * Newer devices (not yet in place) should use the range
985: * of 128-191 and just pick the first free minor there
986: * (new style). */
987: switch (type) {
988: case VFL_TYPE_VIDEO:
989: minor_offset = 0;
990: minor_cnt = 64;
991: break;
992: case VFL_TYPE_RADIO:
993: minor_offset = 64;
994: minor_cnt = 64;
995: break;
996: case VFL_TYPE_VBI:
997: minor_offset = 224;
998: minor_cnt = 32;
999: break;
1000: default:
1001: minor_offset = 128;
1002: minor_cnt = 64;
1003: break;
1004: }
1005: #endif
1006:
1007: /* Pick a device node number */
1008: mutex_lock(&videodev_lock);
1009: nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
1010: if (nr == minor_cnt)
1011: nr = devnode_find(vdev, 0, minor_cnt);
1012: if (nr == minor_cnt) {
1013: pr_err("could not get a free device node number\n");
1014: mutex_unlock(&videodev_lock);
1015: return -ENFILE;
1016: }
1017: #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
1018: /* 1-on-1 mapping of device node number to minor number */
1019: i = nr;
1020: #else
1021: /* The device node number and minor numbers are independent, so
1022: we just find the first free minor number. */
1023: for (i = 0; i < VIDEO_NUM_DEVICES; i++)
1024: if (video_devices[i] == NULL)
1025: break;
1026: if (i == VIDEO_NUM_DEVICES) {
1027: mutex_unlock(&videodev_lock);
1028: pr_err("could not get a free minor\n");
1029: return -ENFILE;
1030: }
1031: #endif
1032: vdev->minor = i + minor_offset;
1033: vdev->num = nr;
1034:
1035: if (WARN_ON(vdev->minor >= VIDEO_NUM_DEVICES)) {
1036: mutex_unlock(&videodev_lock);
1037: return -EINVAL;
1038: }
1039:
1040: /* Should not happen since we thought this minor was free */
1041: if (WARN_ON(video_devices[vdev->minor])) {
1042: mutex_unlock(&videodev_lock);
1043: pr_err("video_device not empty!\n");
1044: return -ENFILE;
1045: }
1046: devnode_set(vdev);
1047: vdev->index = get_index(vdev);
1048: video_devices[vdev->minor] = vdev;
1049: mutex_unlock(&videodev_lock);
1050:
1051: if (vdev->ioctl_ops)
1052: determine_valid_ioctls(vdev);
1053:
1054: /* Part 3: Initialize the character device */
1055: vdev->cdev = cdev_alloc();
1056: if (vdev->cdev == NULL) {
1057: ret = -ENOMEM;
1058: goto cleanup;
1059: }
1060: vdev->cdev->ops = &v4l2_fops;
1061: vdev->cdev->owner = owner;
1062: ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
1063: if (ret < 0) {
1064: pr_err("%s: cdev_add failed\n", __func__);
1065: kfree(vdev->cdev);
1066: vdev->cdev = NULL;
1067: goto cleanup;
1068: }
1069:
1070: /* Part 4: register the device with sysfs */
1071: vdev->dev.class = &video_class;
1072: vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
1073: vdev->dev.parent = vdev->dev_parent;
1074: vdev->dev.release = v4l2_device_release;
1075: dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
1076:
1077: /* Increase v4l2_device refcount */
1078: v4l2_device_get(vdev->v4l2_dev);
1079:
1080: mutex_lock(&videodev_lock);
1081: ret = device_register(&vdev->dev);
1082: if (ret < 0) {
1083: mutex_unlock(&videodev_lock);
1084: pr_err("%s: device_register failed\n", __func__);
1085: put_device(&vdev->dev);
1086: return ret;
1087: }
1088:
1089: if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
1090: pr_warn("%s: requested %s%d, got %s\n", __func__,
1091: name_base, nr, video_device_node_name(vdev));
1092:
1093: /* Part 5: Register the entity. */
1094: ret = video_register_media_controller(vdev);
1095:
1096: /* Part 6: Activate this minor. The char device can now be used. */
1097: set_bit(V4L2_FL_REGISTERED, &vdev->flags);
1098: mutex_unlock(&videodev_lock);
1099:
1100: return 0;
1101:
1102: cleanup:
1103: mutex_lock(&videodev_lock);
1104: if (vdev->cdev)
1105: cdev_del(vdev->cdev);
1106: video_devices[vdev->minor] = NULL;
1107: devnode_clear(vdev);
1108: mutex_unlock(&videodev_lock);
1109: /* Mark this video device as never having been registered. */
1110: vdev->minor = -1;
1111: return ret;
1112: }
SourceFile:drivers/media/v4l2-core/v4l2-dev.c]
|
| 40/2 |
2026/08/09 09:40 |
llm |
expert |
0m
Model:
Tokens:
input: 46442
output: 41
thoughts: 750
Thoughts: **Debugging the `sur40_probe` Failure**
Okay, let's break this down. `video_register_device` can fail, sure, if `cdev_alloc`, `cdev_add`, or `device_register` throw an error with ENOMEM or something. But if it fails, the code handles it β `sur40_probe` just goes to the error path, which makes sense. So why did it fail in that syzkaller reproducer? Perhaps something *earlier* in `sur40_probe` is the culprit.
Let's trace it back. Okay, `sur40_probe` calls `v4l2_device_register`, and *then* proceeds to `vb2_queue_init` if that succeeds. But there is another section! `sur40_probe` has this:
```c
sur40->bulk_in_size = usb_endpoint_maxp(endpoint);
sur40->bulk_in_epaddr = endpoint->bEndpointAddress;
sur40->bulk_in_buffer = kmalloc(sur40->bulk_in_size, GFP_KERNEL);
```
What if `usb_endpoint_maxp(endpoint)` returns 0? `kmalloc(0)` returns `ZERO_SIZE_PTR`. In that case, `input_register_device` still succeeds, `v4l2_device_register` also then succeeds.
Wait! What if `sur40_probe` is failing in the initialization of the V4L2 controls, specifically in the call to `v4l2_ctrl_new_std`?
```c
v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_BRIGHTNESS,
SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX, 1, clamp(brightness,
(uint)SUR40_BRIGHTNESS_MIN, (uint)SUR40_BRIGHTNESS_MAX));
```
If that fails, perhaps due to memory allocation, `sur40->hdl.error` gets set. But the syzkaller reproducer isn't injecting faults here, so that seems unlikely.
What about a device node conflict for `video_register_device`? But the minor number (`nr`) is set to -1. That suggests it should be taking the first available minor number.
Okay, what about the `cdev_alloc` failing? Possible under memory pressure, but syzkaller usually isn't that fragile unless it's designed to specifically test memory issues.
Let's look at that reproducer again...
```c
memcpy((void*)0x200000000100, "... [truncated large byte array] ...", 71);
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x47, /*dev=*/0x200000000100, /*conn_descs=*/0);
```
It just connects the USB device. So what could go wrong within `sur40_probe`? I need to go through it, line by line. Let's start with `sur40_probe` again.
|
| 41/2 |
2026/08/09 09:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_probe]
Results: map[SourceCode: 650: /* Check candidate USB interface. */
651: static int sur40_probe(struct usb_interface *interface,
652: const struct usb_device_id *id)
653: {
654: struct usb_device *usbdev = interface_to_usbdev(interface);
655: struct sur40_state *sur40;
656: struct usb_host_interface *iface_desc;
657: struct usb_endpoint_descriptor *endpoint;
658: struct input_dev *input;
659: int error;
660:
661: /* Check if we really have the right interface. */
662: iface_desc = interface->cur_altsetting;
663: if (iface_desc->desc.bInterfaceClass != 0xFF)
664: return -ENODEV;
665:
666: if (iface_desc->desc.bNumEndpoints < 5)
667: return -ENODEV;
668:
669: /* Use endpoint #4 (0x86). */
670: endpoint = &iface_desc->endpoint[4].desc;
671: if (endpoint->bEndpointAddress != TOUCH_ENDPOINT)
672: return -ENODEV;
673:
674: /* Allocate memory for our device state and initialize it. */
675: sur40 = kzalloc_obj(*sur40);
676: if (!sur40)
677: return -ENOMEM;
678:
679: input = input_allocate_device();
680: if (!input) {
681: error = -ENOMEM;
682: goto err_free_dev;
683: }
684:
685: /* initialize locks/lists */
686: INIT_LIST_HEAD(&sur40->buf_list);
687: spin_lock_init(&sur40->qlock);
688: mutex_init(&sur40->lock);
689:
690: /* Set up regular input device structure */
691: input->name = DRIVER_LONG;
692: usb_to_input_id(usbdev, &input->id);
693: usb_make_path(usbdev, sur40->phys, sizeof(sur40->phys));
694: strlcat(sur40->phys, "/input0", sizeof(sur40->phys));
695: input->phys = sur40->phys;
696: input->dev.parent = &interface->dev;
697:
698: input->open = sur40_open;
699: input->close = sur40_close;
700:
701: error = sur40_input_setup_events(input);
702: if (error)
703: goto err_free_input;
704:
705: input_set_drvdata(input, sur40);
706: error = input_setup_polling(input, sur40_poll);
707: if (error) {
708: dev_err(&interface->dev, "failed to set up polling");
709: goto err_free_input;
710: }
711:
712: input_set_poll_interval(input, POLL_INTERVAL);
713:
714: sur40->usbdev = usbdev;
715: sur40->dev = &interface->dev;
716: sur40->input = input;
717:
718: /* use the bulk-in endpoint tested above */
719: sur40->bulk_in_size = usb_endpoint_maxp(endpoint);
720: sur40->bulk_in_epaddr = endpoint->bEndpointAddress;
721: sur40->bulk_in_buffer = kmalloc(sur40->bulk_in_size, GFP_KERNEL);
722: if (!sur40->bulk_in_buffer) {
723: dev_err(&interface->dev, "Unable to allocate input buffer.");
724: error = -ENOMEM;
725: goto err_free_input;
726: }
727:
728: /* register the polled input device */
729: error = input_register_device(input);
730: if (error) {
731: dev_err(&interface->dev,
732: "Unable to register polled input device.");
733: goto err_free_buffer;
734: }
735:
736: /* register the video master device */
737: snprintf(sur40->v4l2.name, sizeof(sur40->v4l2.name), "%s", DRIVER_LONG);
738: error = v4l2_device_register(sur40->dev, &sur40->v4l2);
739: if (error) {
740: dev_err(&interface->dev,
741: "Unable to register video master device.");
742: goto err_unreg_v4l2;
743: }
744:
745: /* initialize the lock and subdevice */
746: sur40->queue = sur40_queue;
747: sur40->queue.drv_priv = sur40;
748: sur40->queue.lock = &sur40->lock;
749: sur40->queue.dev = sur40->dev;
750:
751: /* initialize the queue */
752: error = vb2_queue_init(&sur40->queue);
753: if (error)
754: goto err_unreg_v4l2;
755:
756: sur40->pix_fmt = sur40_pix_format[0];
757: sur40->vdev = sur40_video_device;
758: sur40->vdev.v4l2_dev = &sur40->v4l2;
759: sur40->vdev.lock = &sur40->lock;
760: sur40->vdev.queue = &sur40->queue;
761: video_set_drvdata(&sur40->vdev, sur40);
762:
763: /* initialize the control handler for 4 controls */
764: v4l2_ctrl_handler_init(&sur40->hdl, 4);
765: sur40->v4l2.ctrl_handler = &sur40->hdl;
766: sur40->vsvideo = (SUR40_CONTRAST_DEF << 4) | SUR40_GAIN_DEF;
767:
768: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_BRIGHTNESS,
769: SUR40_BRIGHTNESS_MIN, SUR40_BRIGHTNESS_MAX, 1, clamp(brightness,
770: (uint)SUR40_BRIGHTNESS_MIN, (uint)SUR40_BRIGHTNESS_MAX));
771:
772: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_CONTRAST,
773: SUR40_CONTRAST_MIN, SUR40_CONTRAST_MAX, 1, clamp(contrast,
774: (uint)SUR40_CONTRAST_MIN, (uint)SUR40_CONTRAST_MAX));
775:
776: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops, V4L2_CID_GAIN,
777: SUR40_GAIN_MIN, SUR40_GAIN_MAX, 1, clamp(gain,
778: (uint)SUR40_GAIN_MIN, (uint)SUR40_GAIN_MAX));
779:
780: v4l2_ctrl_new_std(&sur40->hdl, &sur40_ctrl_ops,
781: V4L2_CID_BACKLIGHT_COMPENSATION, SUR40_BACKLIGHT_MIN,
782: SUR40_BACKLIGHT_MAX, 1, SUR40_BACKLIGHT_DEF);
783:
784: v4l2_ctrl_handler_setup(&sur40->hdl);
785:
786: if (sur40->hdl.error) {
787: dev_err(&interface->dev,
788: "Unable to register video controls.");
789: v4l2_ctrl_handler_free(&sur40->hdl);
790: error = sur40->hdl.error;
791: goto err_unreg_v4l2;
792: }
793:
794: error = video_register_device(&sur40->vdev, VFL_TYPE_TOUCH, -1);
795: if (error) {
796: dev_err(&interface->dev,
797: "Unable to register video subdevice.");
798: goto err_unreg_video;
799: }
800:
801: /* we can register the device now, as it is ready */
802: usb_set_intfdata(interface, sur40);
803: dev_dbg(&interface->dev, "%s is now attached\n", DRIVER_DESC);
804:
805: return 0;
806:
807: err_unreg_video:
808: video_unregister_device(&sur40->vdev);
809: err_unreg_v4l2:
810: v4l2_device_unregister(&sur40->v4l2);
811: err_free_buffer:
812: kfree(sur40->bulk_in_buffer);
813: err_free_input:
814: input_free_device(input);
815: err_free_dev:
816: kfree(sur40);
817:
818: return error;
819: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 42/2 |
2026/08/09 09:40 |
llm |
expert |
0m
Model:
Tokens:
input: 50935
output: 43
thoughts: 47
Thoughts: **Analyzing the `sur40_probe` Function**
Okay, let's see. My initial glance at the code revealed that `sur40_probe` assigns `sur40_video_device` to the `vdev` member of the `sur40` structure. That's interesting, and it immediately triggers the need to know more about what `sur40_video_device` is. It's the central point of this function, at least in the initial reading. I'm going to shift my focus now to understanding the nature of this `sur40_video_device` variable. What type is it? What does it represent in the system? That's where I need to dig in next.
|
| 43/2 |
2026/08/09 09:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_video_device]
Results: map[SourceCode:1167: static const struct video_device sur40_video_device = {
1168: .name = DRIVER_LONG,
1169: .fops = &sur40_video_fops,
1170: .ioctl_ops = &sur40_video_ioctl_ops,
1171: .release = video_device_release_empty,
1172: .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH |
1173: V4L2_CAP_READWRITE | V4L2_CAP_STREAMING,
1174: };
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 44/2 |
2026/08/09 09:40 |
llm |
expert |
0m
Model:
Tokens:
input: 51383
output: 44
thoughts: 369
Thoughts: **Analyzing the V4L2 Video Device Registration**
Okay, so looking at this `sur40_video_device` struct, its `.device_caps` field is definitely set to include the key capabilities: video capture, touch, read/write, and streaming. Good. Now, I'm thinking about the registration process in `__video_register_device`. The code explicitly checks that the `device_caps` field isn't empty, except for sub-devices, and it *is* set here, so that's fine.
Next, I need to make sure `vdev->release` is implemented. The registration requires a release callback, and I see it's set to `video_device_release_empty`. That's a valid implementation, and it will handle the resource clean up, so that checks out.
Then, there's the `vdev->v4l2_dev` pointer. The code mandates this be present. I remember that from the `sur40_probe` function. I see in the driver code `sur40->vdev.v4l2_dev = &sur40->v4l2;`, so the driver is properly associating the v4l2 device with the video device.
Finally, the `vdev->fops` - the file operations. The code demands an `open` and `release` callback within the `fops` struct. To verify this I'll need to check the `sur40_video_fops`. I'll dig into that struct to confirm the `open` and `release` operations are defined.
|
| 45/2 |
2026/08/09 09:40 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_video_fops]
Results: map[SourceCode:1127: static const struct v4l2_file_operations sur40_video_fops = {
1128: .owner = THIS_MODULE,
1129: .open = v4l2_fh_open,
1130: .release = vb2_fop_release,
1131: .unlocked_ioctl = video_ioctl2,
1132: .read = vb2_fop_read,
1133: .mmap = vb2_fop_mmap,
1134: .poll = vb2_fop_poll,
1135: };
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 46/2 |
2026/08/09 09:40 |
llm |
expert |
0m
Model:
Tokens:
input: 52341
output: 43
thoughts: 400
Thoughts: **Analyzing Potential Failure Points in Video Device Registration**
Okay, so I'm looking at this `__video_register_device` function and it *shouldn't* fail due to the `open` and `release` checks, given the context. But hold on, what about a failure within `vb2_queue_init`? That could definitely be a blocker. Let's dig deeper into that. Specifically, I'm checking the `sur40_queue` definition.
Right, so I see the `vb2_queue_init` call that could error out. Now, let's examine the `sur40_queue` struct itself, to get a better understanding of what parameters are being passed to that function.
Okay, let me check the `vb2_core_queue_init` that the previous function calls. Ah, here's where we get down to brass tacks: There's this series of `WARN_ON` checks within `vb2_core_queue_init` that could return `-EINVAL`. It's checking for the existence of several members within the queue struct. Now, I need to verify that those checks are satisfied in the definition of the `sur40_queue`.
So I'll need to check `sur40_queue_ops` to make sure it includes `queue_setup` and `buf_queue`, since those are vital.
|
| 47/2 |
2026/08/09 09:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_queue_ops]
Results: map[SourceCode:1103: /* V4L2 structures */
1104: static const struct vb2_ops sur40_queue_ops = {
1105: .queue_setup = sur40_queue_setup,
1106: .buf_prepare = sur40_buffer_prepare,
1107: .buf_queue = sur40_buffer_queue,
1108: .start_streaming = sur40_start_streaming,
1109: .stop_streaming = sur40_stop_streaming,
1110: };
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 48/2 |
2026/08/09 09:41 |
llm |
expert |
0m
Model:
Tokens:
input: 53269
output: 43
thoughts: 2423
Thoughts: **Debugging the SUR40 Driver Failure**
Okay, so I'm looking at this KASAN report, and it seems the `sur40_probe` function is freeing `sur40` prematurely. The critical line is at `sur40_probe+0xc36`. Let's break this down. First, I confirmed that `vb2_queue_init` should succeed, the lock is set. It seems all the required allocations and registrations happen. The USB endpoint allocation looks fine too. So, the bug report indicates that `kfree(sur40)` is reached at the end of the error path, not at a normal exit. It seems something failed earlier, but the driver is not correctly handling that failure, and that leads to the free of `sur40` and the reported crash. I am looking into possible reasons for an earlier failure.
I verified the endpoint address. That looks correct. The descriptors are valid. So, `sur40` and `input` should be allocated without an error. So, I checked the control handling. Could `v4l2_ctrl_new_std` be failing? But is a global variable being used? And then checked `sur40->hdl.error`. But the code checks for this case!
Maybe `video_register_device` fails. I checked the `video_device` struct. The `device_caps` field is set, so it's not a missing capability. The code calls `video_register_device` with `VFL_TYPE_TOUCH`. I'm wondering if a fixed minor range issue is causing problems. This seems unlikely. There is a `default` case to handle `VFL_TYPE_TOUCH` if minor ranges are fixed. If `CONFIG_VIDEO_FIXED_MINOR_RANGES` is enabled, the code handles `VFL_TYPE_TOUCH` by falling to the default.
I checked for the v4l2 device and its registration. The `vdev->v4l2_dev` is not NULL. I am checking the `video_register_media_controller()` function now.
I went back and checked `usb_set_intfdata`. No errors there. I looked at the setup of controls, the driver checks for `sur40->hdl.error`. So, it should be working fine. It is possible that some invalid control setup could lead to `sur40->hdl.error` being set.
|
| 49/2 |
2026/08/09 09:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_ctrl_ops]
Results: map[SourceCode: 241: static const struct v4l2_ctrl_ops sur40_ctrl_ops = {
242: .s_ctrl = sur40_s_ctrl,
243: };
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 50/2 |
2026/08/09 09:41 |
llm |
expert |
0m
Model:
Tokens:
input: 56349
output: 43
thoughts: 13
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and written in the first person:
**Analyzing `sur40_s_ctrl`**
Alright, let's dive into this. The initial thought is to take a look at `sur40_s_ctrl`. I'm going to assume this refers to a specific dataset or control condition, probably something experimental or diagnostic. My initial focus here is to understand the function of this dataset - what are we trying to achieve by checking this? Depending on the context, `sur40_s_ctrl` could be: a dataset used to control for a specific variable, a file used to control for an experimental artifact, or a diagnostic set used to see if a system is working properly. So my first question is: what is the overall goal in inspecting `sur40_s_ctrl`? Is it just sanity checking, or are we actively trying to debug something? My next immediate step would depend on that answer. If the context is a script, I would trace back to see how the name `sur40_s_ctrl` is used. What are we trying to accomplish in our investigation? Is there a hypothesis or a specific issue we are trying to troubleshoot?
|
| 51/2 |
2026/08/09 09:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_s_ctrl]
Results: map[SourceCode:1015: static int sur40_s_ctrl(struct v4l2_ctrl *ctrl)
1016: {
1017: struct sur40_state *sur40 = container_of(ctrl->handler,
1018: struct sur40_state, hdl);
1019: u8 value = sur40->vsvideo;
1020:
1021: switch (ctrl->id) {
1022: case V4L2_CID_BRIGHTNESS:
1023: sur40_set_irlevel(sur40, ctrl->val);
1024: break;
1025: case V4L2_CID_CONTRAST:
1026: value = (value & 0x0f) | (ctrl->val << 4);
1027: sur40_set_vsvideo(sur40, value);
1028: break;
1029: case V4L2_CID_GAIN:
1030: value = (value & 0xf0) | (ctrl->val);
1031: sur40_set_vsvideo(sur40, value);
1032: break;
1033: case V4L2_CID_BACKLIGHT_COMPENSATION:
1034: sur40_set_preprocessor(sur40, ctrl->val);
1035: break;
1036: }
1037: return 0;
1038: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 52/2 |
2026/08/09 09:41 |
llm |
expert |
0m
Model:
Tokens:
input: 57107
output: 45
thoughts: 377
Thoughts: **USB Command Execution During Initialization - A Potential Problem**
Okay, so I'm looking at this `sur40` driver, and it's hitting me: `sur40_set_irlevel` and `sur40_set_vsvideo`, they both send USB commands using `usb_control_msg` through `sur40_command`. That's standard practice, but the execution is triggered during `v4l2_ctrl_handler_setup` when controls are initialized via `s_ctrl` functions. That means `sur40_s_ctrl` is called during `sur40_probe` since the controls are set up in the probe function.
Now, if a USB device doesn't respond, or if there's a problem with the USB command itselfβa timeout, a stall, something like thatβ`usb_control_msg` returns an error code, as it should. But here's the kicker: `sur40_s_ctrl` seems to completely ignore the return value of `sur40_set_irlevel`, which does the USB communication! It blindly returns 0, regardless of whether the command succeeded or failed!
This means that `v4l2_ctrl_handler_setup` is not going to fail even if a critical USB command fails during initial setup! My mind is racing, so if that's the case, then *why* does `sur40_probe` sometimes fail? It's not immediately obvious based on this code path. I need to dig deeper. I need to see what `sur40_set_vsvideo` does, which may be where the real probe failures can occur.
|
| 53/2 |
2026/08/09 09:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_set_vsvideo]
Results: map[SourceCode: 322: static void sur40_set_vsvideo(struct sur40_state *handle, u8 value)
323: {
324: int i;
325:
326: for (i = 0; i < 4; i++)
327: sur40_poke(handle, 0x1c+i, value);
328: handle->vsvideo = value;
329: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 54/2 |
2026/08/09 09:41 |
llm |
expert |
0m
Model:
Tokens:
input: 58050
output: 44
thoughts: 21
Thoughts: Here's my take on those thoughts:
**Considering Preprocessing and Return Values**
Okay, so I'm processing this code and I've hit a point where the logic just completely ignores the return value of a function. That's a red flag. In my experience, you *never* want to casually disregard the output of a function, especially in a critical path. It could indicate an issue with error handling, a design flaw where the return value is actually crucial information, or even a subtle bug waiting to bite us later. I need to dig deeper to understand *why* it's being done this way. Are there other means of checking whether the function worked?
Then, my mind jumps to `sur40_set_preprocessor`. Iβve worked with that function before and I'm instantly wondering if this particular function call is somehow related. Is it a similar piece of the architecture, maybe a related component, that's also silently dropping crucial return data? I need to analyze its use too to ensure that there are no subtle, related problems. I need to make sure I don't overlook a potential issue here. I need to understand what's going on.
|
| 55/2 |
2026/08/09 09:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_set_preprocessor]
Results: map[SourceCode: 295: static int sur40_set_preprocessor(struct sur40_state *dev, u8 value)
296: {
297: u8 setting_07[2] = { 0x01, 0x00 };
298: u8 setting_17[2] = { 0x85, 0x80 };
299: int result;
300:
301: if (value > 1)
302: return -ERANGE;
303:
304: result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
305: SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
306: 0x07, setting_07[value], NULL, 0, 1000);
307: if (result < 0)
308: goto error;
309: msleep(5);
310:
311: result = usb_control_msg(dev->usbdev, usb_sndctrlpipe(dev->usbdev, 0),
312: SUR40_POKE, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
313: 0x17, setting_17[value], NULL, 0, 1000);
314: if (result < 0)
315: goto error;
316: msleep(5);
317:
318: error:
319: return result;
320: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 56/2 |
2026/08/09 09:41 |
llm |
expert |
0m
Model:
Tokens:
input: 58854
output: 41
thoughts: 435
Thoughts: **Debugging the SUR40 Driver**
Okay, so the `sur40_set_preprocessor` function is throwing an error, that's clear. But *ugh*, `sur40_s_ctrl` completely ignores the return value from `sur40_set_preprocessor`. What a mess! This means `sur40_s_ctrl` always returns 0, no matter what, so `v4l2_ctrl_handler_setup` isn't going to fail because of this particular issue. That simplifies things a *bit*.
But wait... what's another common source of failure? Ah, right! `video_register_device`. I need to figure out why *that* might be failing. Okay, scanning `sur40_probe` again... Here it is:
```c
error = video_register_device(&sur40->vdev, VFL_TYPE_TOUCH, -1);
if (error) {
dev_err(&interface->dev,
"Unable to register video subdevice.");
goto err_unreg_video;
}
```
It looks like if `video_register_device` fails, it goes to `err_unreg_video`. Right, so what are the possible reasons `video_register_device` itself might fail? Things like `cdev_alloc` failing. And maybe `device_register`? Wait, if `device_register` fails, it *does* return an error, but the reproducer isn't specifically targeting that...
Hang on, let's look at the syzkaller reproducer again. It's just a simple device connection.
```c
syz_usb_connect(/*speed=*/0, /*dev_len=*/0x47, /*dev=*/0x200000000100, /*conn_descs=*/0);
```
Okay, so we connect the device. Does the device maybe get disconnected immediately? If the device gets disconnected, that would call `sur40_disconnect`. I should probably check `sur40_disconnect`!
|
| 57/2 |
2026/08/09 09:41 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_disconnect]
Results: map[SourceCode: 821: /* Unregister device & clean up. */
822: static void sur40_disconnect(struct usb_interface *interface)
823: {
824: struct sur40_state *sur40 = usb_get_intfdata(interface);
825:
826: v4l2_ctrl_handler_free(&sur40->hdl);
827: video_unregister_device(&sur40->vdev);
828: v4l2_device_unregister(&sur40->v4l2);
829:
830: input_unregister_device(sur40->input);
831: kfree(sur40->bulk_in_buffer);
832: kfree(sur40);
833:
834: usb_set_intfdata(interface, NULL);
835: dev_dbg(&interface->dev, "%s is now disconnected\n", DRIVER_DESC);
836: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 58/2 |
2026/08/09 09:41 |
llm |
expert |
0m
Model:
Tokens:
input: 60100
output: 49
thoughts: 1134
Thoughts: **Analyzing a Potential Race Condition and Memory Corruption in a Touchscreen Driver**
Okay, so I'm looking at this `sur40` driver, and it looks like there's a serious flaw. The `sur40_disconnect` function calls `input_unregister_device(sur40->input)` and then immediately `kfree(sur40)`. That's a red flag right away. The `input_unregister_device` call *removes* the device, but the `input_dev` structure might still be in use if someone has the device open. And here we are, blindly freeing `sur40`! If someone is calling `sur40_open`, `sur40_close`, or even `sur40_poll`, while this happens, we've got a problem. `input_unregister_device` *should* prevent *new* opens, but what about concurrent operations?
The thing that has me most worried is the possibility of a race. If `sur40_open` gets called concurrently with `sur40_disconnect`, we are in trouble. What if `sur40_probe` fails? The bug report suggests it was freed in `sur40_probe`, which is odd.
The stack trace from the bug report clearly shows the free happening in `sur40_probe`, not `sur40_disconnect`! Why is `sur40_probe` failing? It must be an error somewhere in this function. Let's look at the code again. It seems that this function is trying to register a video device with `video_register_device`. If that fails, it goes to `err_unreg_video`.
So, the code checks the result of `video_register_device`. The code has this `usb_set_intfdata(interface, sur40)` right after `video_register_device`. This means that if it succeeds it returns zero. Could `video_register_device` fail if `VFL_TYPE_TOUCH` is unsupported? That's not likely since `VFL_TYPE_TOUCH` is 3. What could be the issue? I'm looking at the `__video_register_device` function, and it seems like the device node number might be exhausted. It calls `devnode_find`, and if that fails, it returns `-ENFILE`. But Syzkaller isn't creating so many touch devices. Hmm, that's not it.
Wait, `video_register_device` calls `video_register_media_controller`. So the return value is not from `video_register_media_controller`?
The code seems to return 0 even if `video_register_media_controller` fails. That's also probably not the issue. Time to check `__video_register_device` one more time.
|
| 59/2 |
2026/08/09 09:42 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/media/v4l2-core/v4l2-dev.c Name:__video_register_device]
Results: map[SourceCode: 910: int __video_register_device(struct video_device *vdev,
911: enum vfl_devnode_type type,
912: int nr, int warn_if_nr_in_use,
913: struct module *owner)
914: {
915: int i = 0;
916: int ret;
917: int minor_offset = 0;
918: int minor_cnt = VIDEO_NUM_DEVICES;
919: const char *name_base;
920:
921: /* A minor value of -1 marks this video device as never
922: having been registered */
923: vdev->minor = -1;
924:
925: /* the release callback MUST be present */
926: if (WARN_ON(!vdev->release))
927: return -EINVAL;
928: /* the v4l2_dev pointer MUST be present */
929: if (WARN_ON(!vdev->v4l2_dev))
930: return -EINVAL;
931: /* the device_caps field MUST be set for all but subdevs */
932: if (WARN_ON(type != VFL_TYPE_SUBDEV && !vdev->device_caps))
933: return -EINVAL;
934: /* the open and release file operations are mandatory */
935: if (WARN_ON(!vdev->fops || !vdev->fops->open || !vdev->fops->release))
936: return -EINVAL;
937:
938: /* v4l2_fh support */
939: spin_lock_init(&vdev->fh_lock);
940: INIT_LIST_HEAD(&vdev->fh_list);
941:
942: /* Part 1: check device type */
943: switch (type) {
944: case VFL_TYPE_VIDEO:
945: name_base = "video";
946: break;
947: case VFL_TYPE_VBI:
948: name_base = "vbi";
949: break;
950: case VFL_TYPE_RADIO:
951: name_base = "radio";
952: break;
953: case VFL_TYPE_SUBDEV:
954: name_base = "v4l-subdev";
955: break;
956: case VFL_TYPE_SDR:
957: /* Use device name 'swradio' because 'sdr' was already taken. */
958: name_base = "swradio";
959: break;
960: case VFL_TYPE_TOUCH:
961: name_base = "v4l-touch";
962: break;
963: default:
964: pr_err("%s called with unknown type: %d\n",
965: __func__, type);
966: return -EINVAL;
967: }
968:
969: vdev->vfl_type = type;
970: vdev->cdev = NULL;
971: if (vdev->dev_parent == NULL)
972: vdev->dev_parent = vdev->v4l2_dev->dev;
973: if (vdev->ctrl_handler == NULL)
974: vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
975: /* If the prio state pointer is NULL, then use the v4l2_device
976: prio state. */
977: if (vdev->prio == NULL)
978: vdev->prio = &vdev->v4l2_dev->prio;
979:
980: /* Part 2: find a free minor, device node number and device index. */
981: #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
982: /* Keep the ranges for the first four types for historical
983: * reasons.
984: * Newer devices (not yet in place) should use the range
985: * of 128-191 and just pick the first free minor there
986: * (new style). */
987: switch (type) {
988: case VFL_TYPE_VIDEO:
989: minor_offset = 0;
990: minor_cnt = 64;
991: break;
992: case VFL_TYPE_RADIO:
993: minor_offset = 64;
994: minor_cnt = 64;
995: break;
996: case VFL_TYPE_VBI:
997: minor_offset = 224;
998: minor_cnt = 32;
999: break;
1000: default:
1001: minor_offset = 128;
1002: minor_cnt = 64;
1003: break;
1004: }
1005: #endif
1006:
1007: /* Pick a device node number */
1008: mutex_lock(&videodev_lock);
1009: nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
1010: if (nr == minor_cnt)
1011: nr = devnode_find(vdev, 0, minor_cnt);
1012: if (nr == minor_cnt) {
1013: pr_err("could not get a free device node number\n");
1014: mutex_unlock(&videodev_lock);
1015: return -ENFILE;
1016: }
1017: #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
1018: /* 1-on-1 mapping of device node number to minor number */
1019: i = nr;
1020: #else
1021: /* The device node number and minor numbers are independent, so
1022: we just find the first free minor number. */
1023: for (i = 0; i < VIDEO_NUM_DEVICES; i++)
1024: if (video_devices[i] == NULL)
1025: break;
1026: if (i == VIDEO_NUM_DEVICES) {
1027: mutex_unlock(&videodev_lock);
1028: pr_err("could not get a free minor\n");
1029: return -ENFILE;
1030: }
1031: #endif
1032: vdev->minor = i + minor_offset;
1033: vdev->num = nr;
1034:
1035: if (WARN_ON(vdev->minor >= VIDEO_NUM_DEVICES)) {
1036: mutex_unlock(&videodev_lock);
1037: return -EINVAL;
1038: }
1039:
1040: /* Should not happen since we thought this minor was free */
1041: if (WARN_ON(video_devices[vdev->minor])) {
1042: mutex_unlock(&videodev_lock);
1043: pr_err("video_device not empty!\n");
1044: return -ENFILE;
1045: }
1046: devnode_set(vdev);
1047: vdev->index = get_index(vdev);
1048: video_devices[vdev->minor] = vdev;
1049: mutex_unlock(&videodev_lock);
1050:
1051: if (vdev->ioctl_ops)
1052: determine_valid_ioctls(vdev);
1053:
1054: /* Part 3: Initialize the character device */
1055: vdev->cdev = cdev_alloc();
1056: if (vdev->cdev == NULL) {
1057: ret = -ENOMEM;
1058: goto cleanup;
1059: }
1060: vdev->cdev->ops = &v4l2_fops;
1061: vdev->cdev->owner = owner;
1062: ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
1063: if (ret < 0) {
1064: pr_err("%s: cdev_add failed\n", __func__);
1065: kfree(vdev->cdev);
1066: vdev->cdev = NULL;
1067: goto cleanup;
1068: }
1069:
1070: /* Part 4: register the device with sysfs */
1071: vdev->dev.class = &video_class;
1072: vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
1073: vdev->dev.parent = vdev->dev_parent;
1074: vdev->dev.release = v4l2_device_release;
1075: dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
1076:
1077: /* Increase v4l2_device refcount */
1078: v4l2_device_get(vdev->v4l2_dev);
1079:
1080: mutex_lock(&videodev_lock);
1081: ret = device_register(&vdev->dev);
1082: if (ret < 0) {
1083: mutex_unlock(&videodev_lock);
1084: pr_err("%s: device_register failed\n", __func__);
1085: put_device(&vdev->dev);
1086: return ret;
1087: }
1088:
1089: if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
1090: pr_warn("%s: requested %s%d, got %s\n", __func__,
1091: name_base, nr, video_device_node_name(vdev));
1092:
1093: /* Part 5: Register the entity. */
1094: ret = video_register_media_controller(vdev);
1095:
1096: /* Part 6: Activate this minor. The char device can now be used. */
1097: set_bit(V4L2_FL_REGISTERED, &vdev->flags);
1098: mutex_unlock(&videodev_lock);
1099:
1100: return 0;
1101:
1102: cleanup:
1103: mutex_lock(&videodev_lock);
1104: if (vdev->cdev)
1105: cdev_del(vdev->cdev);
1106: video_devices[vdev->minor] = NULL;
1107: devnode_clear(vdev);
1108: mutex_unlock(&videodev_lock);
1109: /* Mark this video device as never having been registered. */
1110: vdev->minor = -1;
1111: return ret;
1112: }
SourceFile:drivers/media/v4l2-core/v4l2-dev.c]
|
| 60/2 |
2026/08/09 09:42 |
llm |
expert |
0m
Model:
Tokens:
input: 65136
output: 45
thoughts: 665
Thoughts: **Analyzing Potential Probe Failures**
Okay, so I'm trying to figure out why this `sur40_probe` might be failing. My initial thought was `device_register` could fail due to resource exhaustion, but that's a red herring. Then, I considered that `v4l2_ctrl_handler_setup`'s failure might be setting `sur40->hdl.error`. But, why would it fail?
Looking into it, the `v4l2_ctrl_new_std` function within `sur40_probe` looks like another potential point of failure; it could return an error and set `sur40->hdl.error`, perhaps due to memory issues. However, the fuzzer (syzkaller) isn't directly injecting faults there. I then asked myself if `v4l2_ctrl_new_std` was failing because the control ID (`V4L2_CID_BRIGHTNESS`) was already added, but I can see it's only added once. What about if the `clamp()` function was being fed values that caused `v4l2_ctrl_new_std` to fail? That can't be it, as `clamp` is designed to keep it within range.
Okay, back to `v4l2_ctrl_handler_setup`. Could `sur40_s_ctrl` return an error that causes `v4l2_ctrl_handler_setup` to fail? Bingo! `sur40_set_preprocessor` returns an error if `usb_control_msg` fails.
However, a closer look at `sur40_s_ctrl` reveals that the return value from `sur40_set_preprocessor` is *ignored*! So `sur40_s_ctrl` always returns 0.
So it's not `v4l2_ctrl_handler_setup`.
Okay, what else could go wrong in `sur40_probe`? What about `usb_set_intfdata` failing? Nope, it returns `void`. What about if `sur40_input_setup_events` fails? That's the next thing to examine.
|
| 61/2 |
2026/08/09 09:42 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/touchscreen/sur40.c Name:sur40_input_setup_events]
Results: map[SourceCode: 616: /* Initialize input device parameters. */
617: static int sur40_input_setup_events(struct input_dev *input_dev)
618: {
619: int error;
620:
621: input_set_abs_params(input_dev, ABS_MT_POSITION_X,
622: 0, SENSOR_RES_X, 0, 0);
623: input_set_abs_params(input_dev, ABS_MT_POSITION_Y,
624: 0, SENSOR_RES_Y, 0, 0);
625:
626: input_set_abs_params(input_dev, ABS_MT_TOOL_X,
627: 0, SENSOR_RES_X, 0, 0);
628: input_set_abs_params(input_dev, ABS_MT_TOOL_Y,
629: 0, SENSOR_RES_Y, 0, 0);
630:
631: /* max value unknown, but major/minor axis
632: * can never be larger than screen */
633: input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR,
634: 0, SENSOR_RES_X, 0, 0);
635: input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR,
636: 0, SENSOR_RES_Y, 0, 0);
637:
638: input_set_abs_params(input_dev, ABS_MT_ORIENTATION, 0, 1, 0, 0);
639:
640: error = input_mt_init_slots(input_dev, MAX_CONTACTS,
641: INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
642: if (error) {
643: dev_err(input_dev->dev.parent, "failed to set up slots\n");
644: return error;
645: }
646:
647: return 0;
648: }
SourceFile:drivers/input/touchscreen/sur40.c]
|
| 62/2 |
2026/08/09 09:42 |
llm |
expert |
0m
Model:
Tokens:
input: 66951
output: 36
thoughts: 139
Thoughts: **Analyzing Potential Failure Points in Input Device Initialization**
Okay, so I'm looking at this `input_mt_init_slots` function, and my initial thought is, "Right, it allocates memory." That means it could fail. But then, I remember we aren't using fault injection with syzkaller in this particular setup. So, memory allocation failure in `input_mt_init_slots` is probably not going to be a direct issue here.
Wait a minute... what about `input_setup_polling`? That's right, the code checks for an error from `input_setup_polling`, and if it fails, it jumps to `err_free_input`. I need to understand that failure path and how it's handled. Is it allocating memory directly? The code block gives the impression it is failing on setting up the polling itself, rather than failing because `poll_timer` memory allocation fails. It only sets up a timer, so it is the setup of the timer itself failing.
Let's dig into the `input_setup_polling` function and its implementation to confirm. This is where the potential problem lies.
|
| 63/2 |
2026/08/09 09:42 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:drivers/input/input.c Name:input_setup_polling]
Results: map[SourceCode: 69: int input_setup_polling(struct input_dev *dev,
70: void (*poll_fn)(struct input_dev *dev))
71: {
72: struct input_dev_poller *poller;
73:
74: poller = kzalloc_obj(*poller);
75: if (!poller) {
76: /*
77: * We want to show message even though kzalloc() may have
78: * printed backtrace as knowing what instance of input
79: * device we were dealing with is helpful.
80: */
81: dev_err(dev->dev.parent ?: &dev->dev,
82: "%s: unable to allocate poller structure\n", __func__);
83: return -ENOMEM;
84: }
85:
86: INIT_DELAYED_WORK(&poller->work, input_dev_poller_work);
87: poller->input = dev;
88: poller->poll = poll_fn;
89:
90: dev->poller = poller;
91: return 0;
92: }
SourceFile:drivers/input/input-poller.c]
|
| 64/2 |
2026/08/09 09:42 |
llm |
expert |
0m
Model:
Tokens:
input: 67723
output: 66
thoughts: 4850
Thoughts: **My Analysis of the USB Driver Vulnerability**
Okay, here's the deal: I've got a handle on this use-after-free in the `sur40` USB driver. It's triggered by a specific error path within the `sur40_probe` function. Specifically, it seems that if `input_register_device` succeeds but something later on in the probe fails, we're in trouble. If `input_register_device` succeeds, it exposes a device to userspace. If a subsequent registration (e.g., `video_register_device`, `v4l2_ctrl_handler_setup`) fails, the error path is triggered. This error path calls `input_free_device`, which is the WRONG function to call after `input_register_device` succeeds. Then, `kfree(sur40)` frees the main `sur40` struct.
The real problem is that calling `input_free_device` after `input_register_device` doesn't unregister the device from userspace. So, a user can open this device, triggering `sur40_open`, which then dereferences the already freed `sur40` structure. It's textbook UAF.
This UAF happens because the driver doesn't properly unregister the device. Because `input_register_device` was called and it was not followed by `input_unregister_device`.
Now, the attacker's power: This vulnerability is exploitable because I can control the content of the freed `sur40` struct because it's a 4096-byte kmalloc object. In the `sur40_open` function, a call to `sur40_init` will call `sur40_command`, and this will pass the `usbdev` pointer from the `sur40` struct to `usb_control_msg`.
Since the attacker controls the freed `sur40` struct, they can manipulate the `usbdev` pointer. If the attacker can point `usbdev` to a place in memory under their control, this gives arbitrary read/write, or control flow hijacking capabilities, and that's an easy local privilege escalation.
Now, how do I trigger this? This is a peripheral trigger. It requires a malicious USB device that causes a later initialization step to fail. Think exhausting video minor numbers, or memory pressure during `v4l2_ctrl_new_std`. Essentially, if something causes `sur40_probe` to fail AFTER `input_register_device`, the bug occurs.
This isn't a vulnerability that an unprivileged process can trigger directly because it requires the device to be plugged in or for root privileges. So, not accessible by unprivileged processes, and not accessible from user namespaces. We're also not talking about VM guest triggers or network-based exploits here. It is also not a remote vulnerability.
The real fix? Simple: Make sure to call `input_unregister_device` in the error path instead of `input_free_device`. Or better, move `input_register_device` to the very end of the probe. I need to make sure I am doing the correct unregistration! That's the key.
|
| 65/2 |
2026/08/09 09:43 |
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]
|
| 66/2 |
2026/08/09 09:43 |
llm |
expert |
0m
Model:
Tokens:
input: 73392
output: 743
thoughts: 13
Thoughts: Okay, here's the summary, rendered as a first-person thought process, aimed at an expert audience:
**Finalizing the Response**
Alright, the data's been crunched, the analysis is complete, and the dust has settled. Now it's time to craft the definitive response. No more iterations, no more refinements β this is the point where I synthesize everything into a concise, accurate, and defensible conclusion. It's a critical moment because the culmination of all this effort is about to be presented. My focus sharpens; the weight of presenting the right answer. The pressure is on to ensure clarity, rigor, and complete accuracy at this pivotal stage, so here goes.
|
| 67/1 |
2026/08/09 09:43 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in the `sur40` touchscreen driver,
specifically in the `sur40_open` function.
When a matching USB device is plugged in, the kernel invokes `sur40_probe`.
During initialization, the driver allocates a `sur40_state` structure (which is
around 3KB and falls into the `kmalloc-4k` slab cache). It then allocates and
registers an input device via `input_register_device(input)`.
As soon as `input_register_device` succeeds, the input device is exposed to
userspace (e.g., `/dev/input/eventX`), and uevents are sent. Userspace daemons
like `udevd` can immediately discover and open the device.
If a subsequent initialization step in `sur40_probe` fails (for example,
`video_register_device` failing due to minor number exhaustion or memory
pressure), the driver jumps to its error-handling path:
```c
err_free_input:
input_free_device(input);
err_free_dev:
kfree(sur40);
```
The bug lies in the use of `input_free_device(input)` instead of
`input_unregister_device(input)`. `input_free_device()` only drops the initial
reference taken during allocation, but because the device was successfully
registered, it remains active and visible to userspace. The driver then frees
the `sur40` structure via `kfree(sur40)`.
When `udevd` (or any other userspace process) opens the lingering input device,
the kernel calls `sur40_open`. This function retrieves the freed `sur40`
structure using `input_get_drvdata(input)` and passes it to `sur40_init`.
`sur40_init` then attempts to send USB control messages by dereferencing
`sur40->usbdev`, resulting in a Use-After-Free.
**Exploitability:**
This bug is **Exploitable**. The freed `sur40_state` object is allocated from
the `kmalloc-4k` cache. An attacker can use heap spraying techniques to
reallocate this 4KB chunk with controlled data before `sur40_open` is triggered.
When `sur40_open` calls `sur40_command`, it passes the attacker-controlled
`sur40->usbdev` pointer to `usb_control_msg()`. The USB core will dereference
this controlled pointer, which provides a strong primitive for arbitrary memory
read/write or control-flow hijacking (e.g., by faking the `usb_device` structure
and its internal function pointers), leading to local privilege escalation.
**Triggering:**
This is a **Peripheral Trigger**. To trigger the bug, an attacker needs to plug
in a malicious USB device that passes the initial endpoint checks in
`sur40_probe` but causes a later initialization step to fail. This can be
reliably achieved by plugging in multiple such devices to exhaust the available
video device minor numbers (64 for `VFL_TYPE_TOUCH`), causing
`video_register_device` to fail on the last one and triggering the erroneous
cleanup path.
Because it requires plugging in a physical USB device (or having root access to
emulate one via `/dev/raw-gadget`), it is not accessible from unprivileged
processes or user namespaces.]
|