[PATCH v4] misc: mei: fix race condition between client teardown and read completion
From: nirbhayykumarr
Date: Mon Aug 24 2026 - 10:36:32 EST
This issue was discovered using a custom multi-threaded C fuzzer
designed to stress-test HECI client lifecycles over /dev/mei0. By
running one thread that streams asynchronous MKHI requests while a
second thread concurrently drives rapid open/close/reconnect cycles,
a race condition is triggered during client teardown.
In mei_release(), a host client is torn down upon close(). During this
sequence, mei_cl_disconnect() is invoked, which temporarily releases
dev->device_lock while waiting for the firmware response.
If an in-flight read request was previously submitted, an incoming
completion interrupt processed concurrently by the MEI interrupt
handler can add a completed callback into cl->rd_completed via
mei_cl_add_rd_completed().
Because mei_cl_flush_queues(cl, NULL) was invoked before
mei_cl_unlink(cl), an incoming completion callback can slip into
cl->rd_completed after the flush has completed but before the client
is unlinked from dev->file_list. When mei_cl_unlink() is subsequently
called, the invariant check at drivers/misc/mei/client.c:698 triggers:
WARN_ON(!list_empty(&cl->rd_completed) ||
!list_empty(&cl->rd_pending) ||
!list_empty(&cl->link));
Call trace:
WARNING: CPU: 2 PID: 5056 at drivers/misc/mei/client.c:698 mei_cl_unlink+0xaa/0x140 [mei]
RIP: 0010:mei_cl_unlink+0xaa/0x140 [mei]
Call Trace:
<TASK>
mei_release+0x202/0x270 [mei]
__fput+0x105/0x2e0
__x64_sys_close+0x90/0x140
do_syscall_64+0xaa/0x660
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Immediately following mei_cl_unlink(), mei_release() calls kfree(cl).
If any remaining or deferred callback references the freed client, a
use-after-free occurs.
Fix this by flushing queues after unlinking the client from
dev->file_list inside mei_cl_unlink(), preventing concurrent IRQ
completions from populating the completed queue during teardown.
Note: An LLM was used solely to assist with the proper phrasing and
formatting of this commit message. The vulnerability discovery,
fuzzer development, and code fix are entirely original author's work.
Fixes: f35fe5f47ed0 ("mei: add a vtag map for each client")
Signed-off-by: Nirbhay Kumar <nirbhayykumarr@xxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
v4:
- Added the fuzzer methodology to the commit message per maintainer request.
- Documented LLM usage for Changelog formatting in the commit message.
- Manually wrapped commit message lines to 72 characters.
v3:
- Removed non-standard Helped-by tags.
v2:
- Removed redundant Reported-by tag.
- Added Fixes tag pointing to commit f35fe5f47ed0.
drivers/misc/mei/client.c | 2 ++
drivers/misc/mei/main.c | 1 -
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c
index 643b003..38b5792 100644
--- a/drivers/misc/mei/client.c
+++ b/drivers/misc/mei/client.c
@@ -695,6 +695,8 @@ int mei_cl_unlink(struct mei_cl *cl)
cl->state = MEI_FILE_UNINITIALIZED;
cl->writing_state = MEI_IDLE;
+ mei_cl_flush_queues(cl, NULL);
+
WARN_ON(!list_empty(&cl->rd_completed) ||
!list_empty(&cl->rd_pending) ||
!list_empty(&cl->link));
diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c
index 4fbf0b3..9e14ab4 100644
--- a/drivers/misc/mei/main.c
+++ b/drivers/misc/mei/main.c
@@ -148,7 +148,6 @@ static int mei_release(struct inode *inode, struct file *file)
goto out;
}
- mei_cl_flush_queues(cl, NULL);
cl_dbg(dev, cl, "removing\n");
mei_cl_unlink(cl);
--
2.55.0