From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4ECE734E74B; Sat, 8 Aug 2026 02:34:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786156491; cv=none; b=Ox5v1i+Dujc8+sXKZx5aGXil6NMIRkxGBqbJ73MmWaHZWWPBX2ELMOYzqqgVrszGuKoHt/3R6JhLXiMnFndYJEWcgz72cfwcYO/Jqa2VIi2nYTX8etAJZZJoS4SNHlYn+F7fwx0bhXbYQ1oybxjmlpYemNWEj4XaZg3Z+dmdPxc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786156491; c=relaxed/simple; bh=261V3UhfgEsljTYdAaXPrPFK1WulRle5IvACDlIm5C0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gkcdF2dcxJtK/08DHDn1OLguGcEjGiUWemHjEld7oxH+i5hLcbu8r7SVOwjc6uv6ZVgMbYnXNmmekrlyPunceIeqZu2loMWOOuwb2X6wpv7RpZI0nl+tG5n13qkVFU1z+cSCEgcU157/OUkpIACo5aVHBbRspUZWA0SWO6fPEpo= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Received: by linux.microsoft.com (Postfix, from userid 1202) id 55FDF20B710C; Fri, 7 Aug 2026 19:34:26 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 55FDF20B710C From: Long Li To: Long Li , Konstantin Taranov , Jakub Kicinski , "David S . Miller" , Paolo Abeni , Eric Dumazet , Andrew Lunn , Jason Gunthorpe , Leon Romanovsky , Haiyang Zhang , "K . Y . Srinivasan" , Wei Liu , Dexuan Cui , shradhagupta@linux.microsoft.com, Simon Horman , ernis@linux.microsoft.com, stephen@networkplumber.org, Dipayaan Roy , Aditya Garg , Kees Cook , Shachar Raindel Cc: netdev@vger.kernel.org, linux-rdma@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH net v4 7/7] net: mana: keep max_num_cqs immutable once cq_table is allocated Date: Fri, 7 Aug 2026 19:34:16 -0700 Message-ID: <20260808023417.1746886-8-longli@microsoft.com> X-Mailer: git-send-email 2.43.7 In-Reply-To: <20260803234355.636038-1-longli@microsoft.com> References: <20260803234355.636038-1-longli@microsoft.com> Precedence: bulk X-Mailing-List: linux-hyperv@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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