* [PATCH] drivers: firmware_loader: fix race between sysfs fallback cleanup and device removal
@ 2026-04-21 13:49 l1za0.sec
2026-04-21 14:14 ` Greg KH
0 siblings, 1 reply; 4+ messages in thread
From: l1za0.sec @ 2026-04-21 13:49 UTC (permalink / raw)
To: mcgrof, russ.weight, gregkh; +Cc: rafael, linux-kernel
From: Haocheng Yu <l1za0.sec@gmail.com>
A WARNING in firmware_fallback_sysfs is reported by a modified
Syzkaller-based kernel fuzzing tool that we developed.
The cause of this issue is a possible race condition between
firmware sysfs fallback cleanup and hot-plug or other device
lifecycle paths.
In this case, fw_load_sysfs_fallback() might run cleanup after the
fallback device has already been removed, i.e., it would execute
device_del(), then dpm_sysfs_remove(), and finally trigger a
warning in sysfs_remove_group().
This problem can be avoided by adding device_is_registered()
check before calling device_del().
Signed-off-by: Haocheng Yu <l1za0.sec@gmail.com>
---
drivers/base/firmware_loader/fallback.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
index bf68e3947814..a8d8c494f82d 100644
--- a/drivers/base/firmware_loader/fallback.c
+++ b/drivers/base/firmware_loader/fallback.c
@@ -116,8 +116,9 @@ static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, long timeout)
} else if (fw_priv->is_paged_buf && !fw_priv->data)
retval = -ENOMEM;
-out:
- device_del(f_dev);
+out:
+ if (device_is_registered(f_dev))
+ device_del(f_dev);
err_put_dev:
put_device(f_dev);
return retval;
base-commit: ffc253263a1375a65fa6c9f62a893e9767fbebfa
--
2.51.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] drivers: firmware_loader: fix race between sysfs fallback cleanup and device removal
2026-04-21 13:49 [PATCH] drivers: firmware_loader: fix race between sysfs fallback cleanup and device removal l1za0.sec
@ 2026-04-21 14:14 ` Greg KH
2026-04-22 11:36 ` Li Zao
0 siblings, 1 reply; 4+ messages in thread
From: Greg KH @ 2026-04-21 14:14 UTC (permalink / raw)
To: l1za0.sec; +Cc: mcgrof, russ.weight, rafael, linux-kernel
On Tue, Apr 21, 2026 at 09:49:57PM +0800, l1za0.sec@gmail.com wrote:
> From: Haocheng Yu <l1za0.sec@gmail.com>
>
> A WARNING in firmware_fallback_sysfs is reported by a modified
> Syzkaller-based kernel fuzzing tool that we developed.
>
> The cause of this issue is a possible race condition between
> firmware sysfs fallback cleanup and hot-plug or other device
> lifecycle paths.
How exactly? This device is under full control of this function, it
should "know" if it is registered or not. No other code path should be
ever unregistering it.
> In this case, fw_load_sysfs_fallback() might run cleanup after the
> fallback device has already been removed, i.e., it would execute
> device_del(), then dpm_sysfs_remove(), and finally trigger a
> warning in sysfs_remove_group().
How exactly does that happen?
> This problem can be avoided by adding device_is_registered()
> check before calling device_del().
>
> Signed-off-by: Haocheng Yu <l1za0.sec@gmail.com>
Do you have a reproducer for this issue?
> ---
> drivers/base/firmware_loader/fallback.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
> index bf68e3947814..a8d8c494f82d 100644
> --- a/drivers/base/firmware_loader/fallback.c
> +++ b/drivers/base/firmware_loader/fallback.c
> @@ -116,8 +116,9 @@ static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, long timeout)
> } else if (fw_priv->is_paged_buf && !fw_priv->data)
> retval = -ENOMEM;
>
> -out:
> - device_del(f_dev);
> +out:
> + if (device_is_registered(f_dev))
> + device_del(f_dev);
Again, this feels wrong. And why was the "out:" line added and added
back? That feels like something is odd with the diff?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] drivers: firmware_loader: fix race between sysfs fallback cleanup and device removal
2026-04-21 14:14 ` Greg KH
@ 2026-04-22 11:36 ` Li Zao
2026-04-22 11:52 ` Greg KH
0 siblings, 1 reply; 4+ messages in thread
From: Li Zao @ 2026-04-22 11:36 UTC (permalink / raw)
To: Greg KH; +Cc: mcgrof, russ.weight, rafael, linux-kernel
In the report, I saw:
sysfs group 'power' not found for kobject 'ueagle-atm!eagleI.fw'
WARNING: CPU: 0 PID: 14408 at fs/sysfs/group.c:282 sysfs_remove_group
fs/sysfs/group.c:282 [inline]
WARNING: CPU: 0 PID: 14408 at fs/sysfs/group.c:282
sysfs_remove_group+0x159/0x1b0 fs/sysfs/group.c:273
The reproducer mainly runs an infinite loop(), which continuously forks
and executes execute_one() within loop(). execute_one() constructs
parameters and calls syz_usb_connect(), causing a large number of
connect and disconnect calls.
According to the call trace, this problem is triggered when
firmware_fallback_sysfs() calls device_del(). So I suppose the problem
is likely caused by a large number of connect/disconnect operations
triggering some kind of race condition, leading to the device being
unregistered and ultimately causing sysfs_remove_group() to remove
an object that no longer exists. But to be honest, since I'm not very
familiar with this code, I can't be 100% sure. Sorry for my lack of rigor.
As for the "out:" line issue, it is indeed a bit strange; I will clean it up in
the next submission.
The complete C reproducer is attached below:
#define _GNU_SOURCE
#include <dirent.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.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/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <linux/usb/ch9.h>
static unsigned long long procid;
static void sleep_ms(uint64_t ms)
{
usleep(ms * 1000);
}
static uint64_t current_time_ms(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
exit(1);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}
static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}
#define MAX_FDS 30
#define USB_MAX_IFACE_NUM 4
#define USB_MAX_EP_NUM 32
#define USB_MAX_FDS 6
struct usb_endpoint_index {
struct usb_endpoint_descriptor desc;
int handle;
};
struct usb_iface_index {
struct usb_interface_descriptor* iface;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bInterfaceClass;
struct usb_endpoint_index eps[USB_MAX_EP_NUM];
int eps_num;
};
struct usb_device_index {
struct usb_device_descriptor* dev;
struct usb_config_descriptor* config;
uint8_t bDeviceClass;
uint8_t bMaxPower;
int config_length;
struct usb_iface_index ifaces[USB_MAX_IFACE_NUM];
int ifaces_num;
int iface_cur;
};
struct usb_info {
int fd;
struct usb_device_index index;
};
static struct usb_info usb_devices[USB_MAX_FDS];
static struct usb_device_index* lookup_usb_index(int fd)
{
for (int i = 0; i < USB_MAX_FDS; i++) {
if (__atomic_load_n(&usb_devices[i].fd, __ATOMIC_ACQUIRE) == fd)
return &usb_devices[i].index;
}
return NULL;
}
static int usb_devices_num;
static bool parse_usb_descriptor(const char* buffer, size_t length,
struct usb_device_index* index)
{
if (length < sizeof(*index->dev) + sizeof(*index->config))
return false;
memset(index, 0, sizeof(*index));
index->dev = (struct usb_device_descriptor*)buffer;
index->config = (struct usb_config_descriptor*)(buffer + sizeof(*index->dev));
index->bDeviceClass = index->dev->bDeviceClass;
index->bMaxPower = index->config->bMaxPower;
index->config_length = length - sizeof(*index->dev);
index->iface_cur = -1;
size_t offset = 0;
while (true) {
if (offset + 1 >= length)
break;
uint8_t desc_length = buffer[offset];
uint8_t desc_type = buffer[offset + 1];
if (desc_length <= 2)
break;
if (offset + desc_length > length)
break;
if (desc_type == USB_DT_INTERFACE &&
index->ifaces_num < USB_MAX_IFACE_NUM) {
struct usb_interface_descriptor* iface =
(struct usb_interface_descriptor*)(buffer + offset);
index->ifaces[index->ifaces_num].iface = iface;
index->ifaces[index->ifaces_num].bInterfaceNumber =
iface->bInterfaceNumber;
index->ifaces[index->ifaces_num].bAlternateSetting =
iface->bAlternateSetting;
index->ifaces[index->ifaces_num].bInterfaceClass = iface->bInterfaceClass;
index->ifaces_num++;
}
if (desc_type == USB_DT_ENDPOINT && index->ifaces_num > 0) {
struct usb_iface_index* iface = &index->ifaces[index->ifaces_num - 1];
if (iface->eps_num < USB_MAX_EP_NUM) {
memcpy(&iface->eps[iface->eps_num].desc, buffer + offset,
sizeof(iface->eps[iface->eps_num].desc));
iface->eps_num++;
}
}
offset += desc_length;
}
return true;
}
static struct usb_device_index* add_usb_index(int fd, const char* dev,
size_t dev_len)
{
int i = __atomic_fetch_add(&usb_devices_num, 1, __ATOMIC_RELAXED);
if (i >= USB_MAX_FDS)
return NULL;
if (!parse_usb_descriptor(dev, dev_len, &usb_devices[i].index))
return NULL;
__atomic_store_n(&usb_devices[i].fd, fd, __ATOMIC_RELEASE);
return &usb_devices[i].index;
}
struct vusb_connect_string_descriptor {
uint32_t len;
char* str;
} __attribute__((packed));
struct vusb_connect_descriptors {
uint32_t qual_len;
char* qual;
uint32_t bos_len;
char* bos;
uint32_t strs_len;
struct vusb_connect_string_descriptor strs[0];
} __attribute__((packed));
static const char default_string[] = {8, USB_DT_STRING, 's', 0, 'y', 0, 'z', 0};
static const char default_lang_id[] = {4, USB_DT_STRING, 0x09, 0x04};
static bool
lookup_connect_response_in(int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl,
struct usb_qualifier_descriptor* qual,
char** response_data, uint32_t* response_length)
{
struct usb_device_index* index = lookup_usb_index(fd);
uint8_t str_idx;
if (!index)
return false;
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_GET_DESCRIPTOR:
switch (ctrl->wValue >> 8) {
case USB_DT_DEVICE:
*response_data = (char*)index->dev;
*response_length = sizeof(*index->dev);
return true;
case USB_DT_CONFIG:
*response_data = (char*)index->config;
*response_length = index->config_length;
return true;
case USB_DT_STRING:
str_idx = (uint8_t)ctrl->wValue;
if (descs && str_idx < descs->strs_len) {
*response_data = descs->strs[str_idx].str;
*response_length = descs->strs[str_idx].len;
return true;
}
if (str_idx == 0) {
*response_data = (char*)&default_lang_id[0];
*response_length = default_lang_id[0];
return true;
}
*response_data = (char*)&default_string[0];
*response_length = default_string[0];
return true;
case USB_DT_BOS:
*response_data = descs->bos;
*response_length = descs->bos_len;
return true;
case USB_DT_DEVICE_QUALIFIER:
if (!descs->qual) {
qual->bLength = sizeof(*qual);
qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
qual->bcdUSB = index->dev->bcdUSB;
qual->bDeviceClass = index->dev->bDeviceClass;
qual->bDeviceSubClass = index->dev->bDeviceSubClass;
qual->bDeviceProtocol = index->dev->bDeviceProtocol;
qual->bMaxPacketSize0 = index->dev->bMaxPacketSize0;
qual->bNumConfigurations = index->dev->bNumConfigurations;
qual->bRESERVED = 0;
*response_data = (char*)qual;
*response_length = sizeof(*qual);
return true;
}
*response_data = descs->qual;
*response_length = descs->qual_len;
return true;
default:
break;
}
break;
default:
break;
}
break;
default:
break;
}
return false;
}
typedef bool (*lookup_connect_out_response_t)(
int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done);
static bool lookup_connect_response_out_generic(
int fd, const struct vusb_connect_descriptors* descs,
const struct usb_ctrlrequest* ctrl, bool* done)
{
switch (ctrl->bRequestType & USB_TYPE_MASK) {
case USB_TYPE_STANDARD:
switch (ctrl->bRequest) {
case USB_REQ_SET_CONFIGURATION:
*done = true;
return true;
default:
break;
}
break;
}
return false;
}
#define UDC_NAME_LENGTH_MAX 128
struct usb_raw_init {
__u8 driver_name[UDC_NAME_LENGTH_MAX];
__u8 device_name[UDC_NAME_LENGTH_MAX];
__u8 speed;
};
enum usb_raw_event_type {
USB_RAW_EVENT_INVALID = 0,
USB_RAW_EVENT_CONNECT = 1,
USB_RAW_EVENT_CONTROL = 2,
};
struct usb_raw_event {
__u32 type;
__u32 length;
__u8 data[0];
};
struct usb_raw_ep_io {
__u16 ep;
__u16 flags;
__u32 length;
__u8 data[0];
};
#define USB_RAW_EPS_NUM_MAX 30
#define USB_RAW_EP_NAME_MAX 16
#define USB_RAW_EP_ADDR_ANY 0xff
struct usb_raw_ep_caps {
__u32 type_control : 1;
__u32 type_iso : 1;
__u32 type_bulk : 1;
__u32 type_int : 1;
__u32 dir_in : 1;
__u32 dir_out : 1;
};
struct usb_raw_ep_limits {
__u16 maxpacket_limit;
__u16 max_streams;
__u32 reserved;
};
struct usb_raw_ep_info {
__u8 name[USB_RAW_EP_NAME_MAX];
__u32 addr;
struct usb_raw_ep_caps caps;
struct usb_raw_ep_limits limits;
};
struct usb_raw_eps_info {
struct usb_raw_ep_info eps[USB_RAW_EPS_NUM_MAX];
};
#define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init)
#define USB_RAW_IOCTL_RUN _IO('U', 1)
#define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event)
#define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor)
#define USB_RAW_IOCTL_EP_DISABLE _IOW('U', 6, __u32)
#define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_EP_READ _IOWR('U', 8, struct usb_raw_ep_io)
#define USB_RAW_IOCTL_CONFIGURE _IO('U', 9)
#define USB_RAW_IOCTL_VBUS_DRAW _IOW('U', 10, __u32)
#define USB_RAW_IOCTL_EPS_INFO _IOR('U', 11, struct usb_raw_eps_info)
#define USB_RAW_IOCTL_EP0_STALL _IO('U', 12)
#define USB_RAW_IOCTL_EP_SET_HALT _IOW('U', 13, __u32)
#define USB_RAW_IOCTL_EP_CLEAR_HALT _IOW('U', 14, __u32)
#define USB_RAW_IOCTL_EP_SET_WEDGE _IOW('U', 15, __u32)
static int usb_raw_open()
{
return open("/dev/raw-gadget", O_RDWR);
}
static int usb_raw_init(int fd, uint32_t speed, const char* driver,
const char* device)
{
struct usb_raw_init arg;
strncpy((char*)&arg.driver_name[0], driver, sizeof(arg.driver_name));
strncpy((char*)&arg.device_name[0], device, sizeof(arg.device_name));
arg.speed = speed;
return ioctl(fd, USB_RAW_IOCTL_INIT, &arg);
}
static int usb_raw_run(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_RUN, 0);
}
static int usb_raw_configure(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0);
}
static int usb_raw_vbus_draw(int fd, uint32_t power)
{
return ioctl(fd, USB_RAW_IOCTL_VBUS_DRAW, power);
}
static int usb_raw_ep0_write(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io);
}
static int usb_raw_ep0_read(int fd, struct usb_raw_ep_io* io)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_READ, io);
}
static int usb_raw_event_fetch(int fd, struct usb_raw_event* event)
{
return ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event);
}
static int usb_raw_ep_enable(int fd, struct usb_endpoint_descriptor* desc)
{
return ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, desc);
}
static int usb_raw_ep_disable(int fd, int ep)
{
return ioctl(fd, USB_RAW_IOCTL_EP_DISABLE, ep);
}
static int usb_raw_ep0_stall(int fd)
{
return ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0);
}
#define USB_MAX_PACKET_SIZE 4096
struct usb_raw_control_event {
struct usb_raw_event inner;
struct usb_ctrlrequest ctrl;
char data[USB_MAX_PACKET_SIZE];
};
struct usb_raw_ep_io_data {
struct usb_raw_ep_io inner;
char data[USB_MAX_PACKET_SIZE];
};
static void set_interface(int fd, int n)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return;
if (index->iface_cur >= 0 && index->iface_cur < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[index->iface_cur].eps_num; ep++) {
int rv = usb_raw_ep_disable(
fd, index->ifaces[index->iface_cur].eps[ep].handle);
if (rv < 0) {
} else {
}
}
}
if (n >= 0 && n < index->ifaces_num) {
for (int ep = 0; ep < index->ifaces[n].eps_num; ep++) {
int rv = usb_raw_ep_enable(fd, &index->ifaces[n].eps[ep].desc);
if (rv < 0) {
} else {
index->ifaces[n].eps[ep].handle = rv;
}
}
index->iface_cur = n;
}
}
static int configure_device(int fd)
{
struct usb_device_index* index = lookup_usb_index(fd);
if (!index)
return -1;
int rv = usb_raw_vbus_draw(fd, index->bMaxPower);
if (rv < 0) {
return rv;
}
rv = usb_raw_configure(fd);
if (rv < 0) {
return rv;
}
set_interface(fd, 0);
return 0;
}
static volatile long
syz_usb_connect_impl(uint64_t speed, uint64_t dev_len, const char* dev,
const struct vusb_connect_descriptors* descs,
lookup_connect_out_response_t lookup_connect_response_out)
{
if (!dev) {
return -1;
}
int fd = usb_raw_open();
if (fd < 0) {
return fd;
}
if (fd >= MAX_FDS) {
close(fd);
return -1;
}
struct usb_device_index* index = add_usb_index(fd, dev, dev_len);
if (!index) {
return -1;
}
char device[32];
sprintf(&device[0], "dummy_udc.%llu", procid);
int rv = usb_raw_init(fd, speed, "dummy_udc", &device[0]);
if (rv < 0) {
return rv;
}
rv = usb_raw_run(fd);
if (rv < 0) {
return rv;
}
bool done = false;
while (!done) {
struct usb_raw_control_event event;
event.inner.type = 0;
event.inner.length = sizeof(event.ctrl);
rv = usb_raw_event_fetch(fd, (struct usb_raw_event*)&event);
if (rv < 0) {
return rv;
}
if (event.inner.type != USB_RAW_EVENT_CONTROL)
continue;
char* response_data = NULL;
uint32_t response_length = 0;
struct usb_qualifier_descriptor qual;
if (event.ctrl.bRequestType & USB_DIR_IN) {
if (!lookup_connect_response_in(fd, descs, &event.ctrl, &qual,
&response_data, &response_length)) {
usb_raw_ep0_stall(fd);
continue;
}
} else {
if (!lookup_connect_response_out(fd, descs, &event.ctrl, &done)) {
usb_raw_ep0_stall(fd);
continue;
}
response_data = NULL;
response_length = event.ctrl.wLength;
}
if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
event.ctrl.bRequest == USB_REQ_SET_CONFIGURATION) {
rv = configure_device(fd);
if (rv < 0) {
return rv;
}
}
struct usb_raw_ep_io_data response;
response.inner.ep = 0;
response.inner.flags = 0;
if (response_length > sizeof(response.data))
response_length = 0;
if (event.ctrl.wLength < response_length)
response_length = event.ctrl.wLength;
response.inner.length = response_length;
if (response_data)
memcpy(&response.data[0], response_data, response_length);
else
memset(&response.data[0], 0, response_length);
if (event.ctrl.bRequestType & USB_DIR_IN) {
rv = usb_raw_ep0_write(fd, (struct usb_raw_ep_io*)&response);
} else {
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io*)&response);
}
if (rv < 0) {
return rv;
}
}
sleep_ms(200);
return fd;
}
static volatile long syz_usb_connect(volatile long a0, volatile long a1,
volatile long a2, volatile long a3)
{
uint64_t speed = a0;
uint64_t dev_len = a1;
const char* dev = (const char*)a2;
const struct vusb_connect_descriptors* descs =
(const struct vusb_connect_descriptors*)a3;
return syz_usb_connect_impl(speed, dev_len, dev, descs,
&lookup_connect_response_out_generic);
}
static void kill_and_wait(int pid, int* status)
{
kill(-pid, SIGKILL);
kill(pid, SIGKILL);
for (int i = 0; i < 100; i++) {
if (waitpid(-1, status, WNOHANG | __WALL) == pid)
return;
usleep(1000);
}
DIR* dir = opendir("/sys/fs/fuse/connections");
if (dir) {
for (;;) {
struct dirent* ent = readdir(dir);
if (!ent)
break;
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
char abort[300];
snprintf(abort, sizeof(abort), "/sys/fs/fuse/connections/%s/abort",
ent->d_name);
int fd = open(abort, O_WRONLY);
if (fd == -1) {
continue;
}
if (write(fd, abort, 1) < 0) {
}
close(fd);
}
closedir(dir);
} else {
}
while (waitpid(-1, status, __WALL) != pid) {
}
}
static void setup_test()
{
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
setpgrp();
write_file("/proc/self/oom_score_adj", "1000");
}
static void execute_one(void);
#define WAIT_FLAGS __WALL
static void loop(void)
{
int iter = 0;
for (;; iter++) {
int pid = fork();
if (pid < 0)
exit(1);
if (pid == 0) {
setup_test();
execute_one();
exit(0);
}
int status = 0;
uint64_t start = current_time_ms();
for (;;) {
sleep_ms(10);
if (waitpid(-1, &status, WNOHANG | WAIT_FLAGS) == pid)
break;
if (current_time_ms() - start < 5000)
continue;
kill_and_wait(pid, &status);
break;
}
}
}
void execute_one(void)
{
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {
}
// syz_usb_connect arguments: [
// speed: usb_device_speed = 0x3 (8 bytes)
// dev_len: len = 0x24 (8 bytes)
// dev: ptr[in, usb_device_descriptor] {
// usb_device_descriptor {
// inner: usb_device_descriptor_verbose_t[flags[usb_versions, int16],
// 0, 0, 0, flags[usb_device_max_packet_sizes, int8], 0, 0, 0,
// array[usb_config_descriptor, 1]] {
// bLength: const = 0x12 (1 bytes)
// bDescriptorType: const = 0x1 (1 bytes)
// bcdUSB: usb_versions = 0x0 (2 bytes)
// bDeviceClass: const = 0x1 (1 bytes)
// bDeviceSubClass: const = 0x6e (1 bytes)
// bDeviceProtocol: const = 0x80 (1 bytes)
// bMaxPacketSize0: usb_device_max_packet_sizes = 0x8 (1 bytes)
// idVendor: const = 0x1039 (2 bytes)
// idProduct: const = 0x2131 (2 bytes)
// bcdDevice: const = 0xb4c6 (2 bytes)
// iManufacturer: const = 0x0 (1 bytes)
// iProduct: const = 0x0 (1 bytes)
// iSerialNumber: const = 0x0 (1 bytes)
// bNumConfigurations: len = 0x1 (1 bytes)
// configs: array[usb_config_descriptor] {
// usb_config_descriptor {
// inner: usb_config_descriptor_verbose_t[int8, len[interfaces,
// int8], int8, flags[usb_config_attributes, int8], int8,
// array[usb_interface_descriptor, 1:4]] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x2 (1 bytes)
// wTotalLength: len = 0x12 (2 bytes)
// bNumInterfaces: len = 0x1 (1 bytes)
// bConfigurationValue: int8 = 0x9 (1 bytes)
// iConfiguration: int8 = 0x0 (1 bytes)
// bmAttributes: usb_config_attributes = 0x1c0 (1 bytes)
// bMaxPower: int8 = 0x0 (1 bytes)
// interfaces: array[usb_interface_descriptor] {
// usb_interface_descriptor {
// inner: usb_interface_descriptor_verbose_t[int8, int8,
// len[endpoints, int8], const[0, int8], const[0, int8],
// const[0, int8], int8,
// array[usb_interface_extra_descriptor, 0:2],
// array[usb_endpoint_descriptor, 0:16]] {
// bLength: const = 0x9 (1 bytes)
// bDescriptorType: const = 0x4 (1 bytes)
// bInterfaceNumber: int8 = 0x7e (1 bytes)
// bAlternateSetting: int8 = 0x8 (1 bytes)
// bNumEndpoints: len = 0x0 (1 bytes)
// bInterfaceClass: const = 0x5d (1 bytes)
// bInterfaceSubClass: const = 0x1e (1 bytes)
// bInterfaceProtocol: const = 0xfc (1 bytes)
// iInterface: int8 = 0x0 (1 bytes)
// extra: array[usb_interface_extra_descriptor] {
// }
// endpoints: array[usb_endpoint_descriptor] {
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
// conn_descs: nil
// ]
// returns fd_usb
*(uint8_t*)0x200000000140 = 0x12;
*(uint8_t*)0x200000000141 = 1;
*(uint16_t*)0x200000000142 = 0;
*(uint8_t*)0x200000000144 = 1;
*(uint8_t*)0x200000000145 = 0x6e;
*(uint8_t*)0x200000000146 = 0x80;
*(uint8_t*)0x200000000147 = 8;
*(uint16_t*)0x200000000148 = 0x1039;
*(uint16_t*)0x20000000014a = 0x2131;
*(uint16_t*)0x20000000014c = 0xb4c6;
*(uint8_t*)0x20000000014e = 0;
*(uint8_t*)0x20000000014f = 0;
*(uint8_t*)0x200000000150 = 0;
*(uint8_t*)0x200000000151 = 1;
*(uint8_t*)0x200000000152 = 9;
*(uint8_t*)0x200000000153 = 2;
*(uint16_t*)0x200000000154 = 0x12;
*(uint8_t*)0x200000000156 = 1;
*(uint8_t*)0x200000000157 = 9;
*(uint8_t*)0x200000000158 = 0;
*(uint8_t*)0x200000000159 = 0xc0;
*(uint8_t*)0x20000000015a = 0;
*(uint8_t*)0x20000000015b = 9;
*(uint8_t*)0x20000000015c = 4;
*(uint8_t*)0x20000000015d = 0x7e;
*(uint8_t*)0x20000000015e = 8;
*(uint8_t*)0x20000000015f = 0;
*(uint8_t*)0x200000000160 = 0x5d;
*(uint8_t*)0x200000000161 = 0x1e;
*(uint8_t*)0x200000000162 = 0xfc;
*(uint8_t*)0x200000000163 = 0;
syz_usb_connect(/*speed=USB_SPEED_HIGH*/ 3, /*dev_len=*/0x24,
/*dev=*/0x200000000140, /*conn_descs=*/0);
}
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul,
/*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/ 7ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul,
/*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/ 0x32ul,
/*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
loop();
return 0;
}
Best regards,
Haocheng
On Tue, Apr 21, 2026 at 10:14 PM Greg KH <gregkh@linuxfoundation.org> wrote:
>
> On Tue, Apr 21, 2026 at 09:49:57PM +0800, l1za0.sec@gmail.com wrote:
> > From: Haocheng Yu <l1za0.sec@gmail.com>
> >
> > A WARNING in firmware_fallback_sysfs is reported by a modified
> > Syzkaller-based kernel fuzzing tool that we developed.
> >
> > The cause of this issue is a possible race condition between
> > firmware sysfs fallback cleanup and hot-plug or other device
> > lifecycle paths.
>
> How exactly? This device is under full control of this function, it
> should "know" if it is registered or not. No other code path should be
> ever unregistering it.
>
> > In this case, fw_load_sysfs_fallback() might run cleanup after the
> > fallback device has already been removed, i.e., it would execute
> > device_del(), then dpm_sysfs_remove(), and finally trigger a
> > warning in sysfs_remove_group().
>
> How exactly does that happen?
>
> > This problem can be avoided by adding device_is_registered()
> > check before calling device_del().
> >
> > Signed-off-by: Haocheng Yu <l1za0.sec@gmail.com>
>
> Do you have a reproducer for this issue?
>
>
> > ---
> > drivers/base/firmware_loader/fallback.c | 5 +++--
> > 1 file changed, 3 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/base/firmware_loader/fallback.c b/drivers/base/firmware_loader/fallback.c
> > index bf68e3947814..a8d8c494f82d 100644
> > --- a/drivers/base/firmware_loader/fallback.c
> > +++ b/drivers/base/firmware_loader/fallback.c
> > @@ -116,8 +116,9 @@ static int fw_load_sysfs_fallback(struct fw_sysfs *fw_sysfs, long timeout)
> > } else if (fw_priv->is_paged_buf && !fw_priv->data)
> > retval = -ENOMEM;
> >
> > -out:
> > - device_del(f_dev);
> > +out:
> > + if (device_is_registered(f_dev))
> > + device_del(f_dev);
>
> Again, this feels wrong. And why was the "out:" line added and added
> back? That feels like something is odd with the diff?
>
> thanks,
>
> greg k-h
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] drivers: firmware_loader: fix race between sysfs fallback cleanup and device removal
2026-04-22 11:36 ` Li Zao
@ 2026-04-22 11:52 ` Greg KH
0 siblings, 0 replies; 4+ messages in thread
From: Greg KH @ 2026-04-22 11:52 UTC (permalink / raw)
To: Li Zao; +Cc: mcgrof, russ.weight, rafael, linux-kernel
On Wed, Apr 22, 2026 at 07:36:15PM +0800, Li Zao wrote:
> In the report, I saw:
>
> sysfs group 'power' not found for kobject 'ueagle-atm!eagleI.fw'
> WARNING: CPU: 0 PID: 14408 at fs/sysfs/group.c:282 sysfs_remove_group
> fs/sysfs/group.c:282 [inline]
> WARNING: CPU: 0 PID: 14408 at fs/sysfs/group.c:282
> sysfs_remove_group+0x159/0x1b0 fs/sysfs/group.c:273
>
> The reproducer mainly runs an infinite loop(), which continuously forks
> and executes execute_one() within loop(). execute_one() constructs
> parameters and calls syz_usb_connect(), causing a large number of
> connect and disconnect calls.
Of a fake USB atm device, right? ATM USB devices are crazy, if you have
the rights to make/destroy lots of them like this, you are running as
root, and can do worse things :)
Luckily ATM is about to be dropped from the kernel, so all is good...
> According to the call trace, this problem is triggered when
> firmware_fallback_sysfs() calls device_del(). So I suppose the problem
> is likely caused by a large number of connect/disconnect operations
> triggering some kind of race condition, leading to the device being
> unregistered and ultimately causing sysfs_remove_group() to remove
> an object that no longer exists. But to be honest, since I'm not very
> familiar with this code, I can't be 100% sure. Sorry for my lack of rigor.
I'd dig into the ueagle driver and see exactly what it is trying to do
with the firmware loading path there, that's probably the issue here,
not in the firmware core.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-04-22 11:52 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-21 13:49 [PATCH] drivers: firmware_loader: fix race between sysfs fallback cleanup and device removal l1za0.sec
2026-04-21 14:14 ` Greg KH
2026-04-22 11:36 ` Li Zao
2026-04-22 11:52 ` Greg KH
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox