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

From: nirbhayykumarr

Date: Sun Aug 23 2026 - 09:26:39 EST


In mei_release(), a host client is torn down upon close(). During this
teardown sequence, mei_cl_disconnect() is invoked, which 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 client's completed queue during teardown.

Reported-by: Nirbhay Kumar <nirbhayykumarr@xxxxxxxxx>
Signed-off-by: Nirbhay Kumar <nirbhayykumarr@xxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
Thanks to Willy Tarreau and Greg Kroah-Hartman for their review and guidance
on the initial report.

Note: Sent via webmail; I have also attached the raw .patch file as a backup
in case webmail clients introduce any subtle whitespace wrapping.

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.0From 0c86e967996e49cf6818b643e41dff66504c7f07 Mon Sep 17 00:00:00 2001
From: Nirbhay Kumar <nirbhayykumarr@xxxxxxxxx>
Date: Sun, 23 Aug 2026 18:37:53 +0530
Subject: [PATCH] misc: mei: fix race condition between client teardown and
read completion

In mei_release(), a host client is torn down upon close(). During this
teardown sequence, mei_cl_disconnect() is invoked, which 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 client's completed queue during teardown.

Reported-by: Nirbhay Kumar <nirbhayykumarr@xxxxxxxxx>
Signed-off-by: Nirbhay Kumar <nirbhayykumarr@xxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
---
Thanks to Willy Tarreau and Greg Kroah-Hartman for their review and guidance
on the initial report.

Note: Sent via webmail; I have also attached the raw .patch file as a backup
in case webmail clients introduce any subtle whitespace wrapping.

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