From f6df3e96a0470edfdeed7ac9ae0f6f93bcc550ae Mon Sep 17 00:00:00 2001 From: Josh Spaulding Date: Wed, 2 Sep 2026 21:30:00 -0500 Subject: [PATCH] hid: ipts: Fix double list_del race in ipts_mei_search() ipts_mei_search() looks up a matching response under the read side of message_lock, drops the lock, and then re-acquires it for writing to unlink the entry. Two concurrent callers waiting for the same command code (typically the receiver poll thread and a userspace hidraw raw request, both inside ipts_control_send_feedback()) can each find the same entry during their read-locked walk and then both unlink and free it. The second list_del() hits LIST_POISON and, with CONFIG_DEBUG_LIST, oopses the poll kthread: list_del corruption, ...->next is LIST_POISON1 (dead000000000100) kernel BUG at lib/list_debug.c:56! RIP: __list_del_entry_valid_or_report.cold Call Trace: ipts_mei_search+0x83/0x150 [ipts] ipts_mei_recv+0x61/0x190 [ipts] ipts_cmd_recv_timeout+0x2f/0x90 [ipts] ipts_control_send_feedback+0x81/0xc0 [ipts] ipts_receiver_poll_loop.cold+0x89/0x11c [ipts] ipts_thread_runner+0x21/0x40 [ipts] Without CONFIG_DEBUG_LIST the same race shows up as a general protection fault on 0xdead000000000108 in ipts_mei_search(). The other caller is then left blocked in D state inside the driver, which makes every later suspend fail with "tasks refusing to freeze". Seen on resume from s2idle on a Surface Pro 7 while iptsd was (re)started. Hold the write lock across the search and the unlink so that a message can only be claimed by one caller. Signed-off-by: Josh Spaulding --- drivers/hid/ipts/mei.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c index 1e0395ceae4a4..60c2ec51b28f9 100644 --- a/drivers/hid/ipts/mei.c +++ b/drivers/hid/ipts/mei.c @@ -26,13 +26,6 @@ static void locked_list_add(struct list_head *new, struct list_head *head, up_write(lock); } -static void locked_list_del(struct list_head *entry, struct rw_semaphore *lock) -{ - down_write(lock); - list_del(entry); - up_write(lock); -} - static void ipts_mei_incoming(struct mei_cl_device *cldev) { ssize_t ret = 0; @@ -85,7 +78,7 @@ static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, if (!rsp) return -EFAULT; - down_read(&mei->message_lock); + down_write(&mei->message_lock); /* * Iterate over the list of received messages, and check if there is one @@ -96,14 +89,14 @@ static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, break; } - up_read(&mei->message_lock); - /* * If entry is not the list head, this means that the loop above has been stopped early, - * and that we found a matching element. We drop the message from the list and return it. + * and that we found a matching element. We drop the message from the list while still + * holding the lock, so that no other waiter can claim the same entry, and return it. */ if (!list_entry_is_head(entry, &mei->messages, list)) { - locked_list_del(&entry->list, &mei->message_lock); + list_del(&entry->list); + up_write(&mei->message_lock); *rsp = entry->rsp; devm_kfree(&mei->cldev->dev, entry); @@ -111,6 +104,8 @@ static int ipts_mei_search(struct ipts_mei *mei, enum ipts_command_code code, return 0; } + up_write(&mei->message_lock); + return -EAGAIN; }