RE: [PATCH v4] misc: mei: fix race condition between client teardown and read completion

From: Usyskin, Alexander

Date: Sun Aug 30 2026 - 04:36:53 EST


> Subject: [PATCH v4] misc: mei: fix race condition between client teardown and
> read completion
>
> 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);

This change look like no-op in this flow as you moved mei_cl_flush_queues() inside the mei_cl_unlink(),
but mei_cl_unlink() is called here straight after mei_cl_flush_queues() without releasing device_lock.

Seems like the failure is real but analysis and patch is wrong.
I suppose there is another path that call mei_cl_unlink() without proper queue cleanup.

- -
Thanks,
Sasha




> --
> 2.55.0