Re: [PATCH rdma-next] RDMA/mlx5: Free software WCs left on wc_list at CQ destroy
From: Edward Srouji
Date: Sun Nov 08 2026 - 10:59:28 EST
On 11/5/2026 9:42 PM, Leon Romanovsky wrote:
External email: Use caution opening links or attachments
On Thu, Nov 05, 2026 at 12:05:47PM +0200, Edward Srouji wrote:
On 11/4/2026 4:01 PM, Leon Romanovsky wrote:
External email: Use caution opening links or attachments
On Tue, Nov 03, 2026 at 06:55:19PM +0200, Edward Srouji wrote:
Software WCs generated by mlx5_ib_generate_wc() are allocated one at a
time and linked onto cq->wc_list. The only thing that ever frees them is
poll_soft_wc(), i.e. a consumer actually reaping them. If the CQ is
destroyed while entries are still linked, those allocations are leaked.
Is this real issue? Just put WARN_ON(!list_empty(...)) instead.
Thanks
Yes, it's a real issue reachable on a normal device removal.
E.g. ib_mad destroys the GSI QP and frees the CQ without polling it (while
it may have unpolled completions).
"may"? Does it have unpolled completions or not?
You were right to question that in first place :)
On a second look it's really not reachable in practice (wc_list is always empty as unregistering MAD agents waits for every posted send to be polled, and only then ib_mad frees the CQ).
I'll post a v2 with your WARN_ON() suggestion.
I'd say that this is a complementary fix to
https://patch.msgid.link/20261008141209.6923-1-prathameshdeshpande7@xxxxxxxxx
Before it, a pending notify_work still ran and polled from freed memory,
which was the UAF it fixed.
Do you have dmesg stack trace?
Thanks
Drain the list in destroy_cq_kernel(). Nothing can be walking wc_list by
then: the poll context is the only walker, notify_work the only thing
that can re-arm it, and both are stopped before post_destroy_cq() runs.
destroy_cq_kernel() is also the error unwind for mlx5_ib_create_cq(), so
move the INIT_LIST_HEAD() of wc_list up next to the other list
initialisations, ahead of anything that can jump to err_cqb. The driver
object is zero-allocated, and list_for_each_entry_safe() evaluates the
next linkage before testing the loop condition, so draining a wc_list
that is still all zeroes would oops.
Fixes: 25361e02c448 ("IB/mlx5: Generate completions in software")
Reviewed-by: Maher Sanalla <msanalla@xxxxxxxxxx>
Signed-off-by: Edward Srouji <edwards@xxxxxxxxxx>
---
drivers/infiniband/hw/mlx5/cq.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/drivers/infiniband/hw/mlx5/cq.c b/drivers/infiniband/hw/mlx5/cq.c
index 55ffd7d4de517e6ce3f04c98b4edcd94b12e43b3..2f7ac350c3d8f179ad1d4d8c1181b93f3151c7ce 100644
--- a/drivers/infiniband/hw/mlx5/cq.c
+++ b/drivers/infiniband/hw/mlx5/cq.c
@@ -933,8 +933,19 @@ static int create_cq_kernel(struct mlx5_ib_dev *dev, struct mlx5_ib_cq *cq,
return err;
}
+static void drain_soft_wc(struct mlx5_ib_cq *cq)
+{
+ struct mlx5_ib_wc *soft_wc, *next;
+
+ list_for_each_entry_safe(soft_wc, next, &cq->wc_list, list) {
+ list_del(&soft_wc->list);
+ kfree(soft_wc);
+ }
+}
+
static void destroy_cq_kernel(struct mlx5_ib_dev *dev, struct mlx5_ib_cq *cq)
{
+ drain_soft_wc(cq);
free_cq_buf(dev, &cq->buf);
mlx5_db_free(dev->mdev, &cq->db);
}
@@ -1067,6 +1078,7 @@ int mlx5_ib_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr,
spin_lock_init(&cq->lock);
INIT_LIST_HEAD(&cq->list_send_qp);
INIT_LIST_HEAD(&cq->list_recv_qp);
+ INIT_LIST_HEAD(&cq->wc_list);
cqe_size = cache_line_size() == 128 ? 128 : 64;
err = create_cq_kernel(dev, cq, entries, cqe_size, &cqb, &index,
@@ -1102,7 +1114,6 @@ int mlx5_ib_create_cq(struct ib_cq *ibcq, const struct ib_cq_init_attr *attr,
mlx5_ib_dbg(dev, "cqn 0x%x\n", cq->mcq.cqn);
cq->mcq.event = mlx5_ib_cq_event;
- INIT_LIST_HEAD(&cq->wc_list);
kvfree(cqb);
return 0;
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261103-free-sw-wcs-8cf40aa66fd8
Best regards,
--
Edward Srouji <edwards@xxxxxxxxxx>