[PATCH net v5 7/7] net: mana: keep max_num_cqs immutable once cq_table is allocated
flat view
DORMANTno replies
SUPERSEDED
From: Long Li <longli@microsoft.com>
Date: 2026-08-09 19:01:25
Also in:
linux-hyperv, linux-rdma, lkml
Subsystem:
hyper-v/azure core and drivers, networking drivers, networking [general], the rest · Maintainers:
Haiyang Zhang, Wei Liu, Dexuan Cui, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v5 of 4 in this series.
Revisions (4)
- v3 [diff vs current]
- v5 current
- v6 [diff vs current]
- v7 [diff vs current]
mana_hwc_init_event_handler() applied every HWC_INIT_DATA_MAX_NUM_CQS
event straight to gc->max_num_cqs. That handler stays live for the whole
channel lifetime -- it also services runtime reconfig and link events --
so it is not confined to the initial bootstrap.
gc->cq_table is allocated once, sized to the max_num_cqs seen at bootstrap,
and every reader (mana_gd_process_eqe(), mana_create_rxq() and
mana_create_txq()) bounds-checks a CQ index against gc->max_num_cqs before
indexing gc->cq_table. A device -- or a malicious host in a confidential
VM -- that sends a later HWC_INIT_DATA_MAX_NUM_CQS with a larger value
inflates the bound past the allocation. This includes an event timed to
land while mana_hwc_establish_channel() is between reading the count and
publishing cq_table. A subsequent out-of-range CQ id then passes the
bounds check and indexes cq_table out of bounds: an out-of-bounds read in
the EQ fast path, or an out-of-bounds pointer write in
mana_create_rxq()/mana_create_txq(), corrupting guest kernel memory.
Stop writing gc->max_num_cqs from the event handler. Store the reported
value in hwc_init_max_num_cqs, and let mana_hwc_establish_channel() commit
it to gc->max_num_cqs once, from the same snapshot that sizes cq_table.
The handler store uses WRITE_ONCE() and the establish-time read uses
READ_ONCE(), since the two run concurrently (EQ interrupt vs process
context); the single, non-reloadable read is what guarantees the value
that sizes cq_table is the same one published as the bound, even across
the sleeping vcalloc(). gc->max_num_cqs then always matches the
allocation and no later event can change the bound after the table is
published, so the existing bounds checks are sufficient.
Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)")
Signed-off-by: Long Li <longli@microsoft.com>
---
Changes in v5:
- No code changes since v4 (resend as a standalone thread).
Changes in v4:
- New patch in v4, split out of the v3 teardown-safety work in response
to review: gc->max_num_cqs is set once when cq_table is allocated and
never reset, so a spoofed post-init HWC event cannot inflate the
bound past the allocation.
.../net/ethernet/microsoft/mana/hw_channel.c | 34 ++++++++++++++++---
include/net/mana/hw_channel.h | 1 +
2 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c
index 2f0dae353955..03b5e2f02e35 100644
--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c
+++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c@@ -246,7 +246,15 @@ static void mana_hwc_init_event_handler(void *ctx, struct gdma_queue *q_self, break; case HWC_INIT_DATA_MAX_NUM_CQS: - gd->gdma_context->max_num_cqs = val; + /* Store, don't apply: mana_hwc_establish_channel() + * commits this to gc->max_num_cqs once, together + * with sizing cq_table, so a spoofed post-init event + * cannot inflate the bound past the allocation. + * WRITE_ONCE() pairs with the READ_ONCE() there: + * this store runs in EQ interrupt context, + * concurrently with that process-context read. + */ + WRITE_ONCE(hwc->hwc_init_max_num_cqs, val); break; case HWC_INIT_DATA_PDID:
@@ -852,6 +860,8 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth, struct gdma_queue *eq = hwc->cq->gdma_eq; struct gdma_queue *cq = hwc->cq->gdma_cq; struct gdma_queue __rcu **cq_table; + u32 num_cqs; + u32 cq_id; int err; init_completion(&hwc->hwc_init_eqe_comp);
@@ -881,15 +891,29 @@ static int mana_hwc_establish_channel(struct gdma_context *gc, u16 *q_depth, *max_req_msg_size = hwc->hwc_init_max_req_msg_size; *max_resp_msg_size = hwc->hwc_init_max_resp_msg_size; - /* Both were set in mana_hwc_init_event_handler(). */ - if (WARN_ON(cq->id >= gc->max_num_cqs)) + /* Snapshot the device-reported CQ count and CQ id into locals and + * use only the locals below, so the same value that sizes cq_table + * also bounds and indexes it -- even across the sleeping vcalloc(). + * Both fields are written by mana_hwc_init_event_handler() from EQ + * interrupt context: hwc_init_max_num_cqs under WRITE_ONCE() (paired + * here), and cq->id as an ordinary store. READ_ONCE() keeps each + * read tear-free and, crucially, non-reloadable, so a spoofed + * post-init event cannot make the WARN_ON() pass against one value + * while the allocation or the index uses another. + */ + num_cqs = READ_ONCE(hwc->hwc_init_max_num_cqs); + cq_id = READ_ONCE(cq->id); + + if (WARN_ON(cq_id >= num_cqs)) return -EPROTO; - cq_table = vcalloc(gc->max_num_cqs, sizeof(*cq_table)); + cq_table = vcalloc(num_cqs, sizeof(*cq_table)); if (!cq_table) return -ENOMEM; - rcu_assign_pointer(cq_table[cq->id], cq); + gc->max_num_cqs = num_cqs; + + rcu_assign_pointer(cq_table[cq_id], cq); /* Publish the fully-initialised table last; pairs with the * rcu_dereference(gc->cq_table) in mana_gd_process_eqe(). */
diff --git a/include/net/mana/hw_channel.h b/include/net/mana/hw_channel.h
index 23bf83e2a3ec..c275150baf49 100644
--- a/include/net/mana/hw_channel.h
+++ b/include/net/mana/hw_channel.h@@ -203,6 +203,7 @@ struct hw_channel_context { u16 hwc_init_q_depth_max; u32 hwc_init_max_req_msg_size; u32 hwc_init_max_resp_msg_size; + u32 hwc_init_max_num_cqs; struct completion hwc_init_eqe_comp;
--
2.43.0