From nobody Tue Sep 29 13:20:39 2026 Received: from canpmsgout03.his.huawei.com (canpmsgout03.his.huawei.com [113.46.200.218]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D7423FE644; Fri, 7 Aug 2026 11:49:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=113.46.200.218 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786103371; cv=none; b=fkOADYQ/F9gfjP+SrxM5PBEeaFipquCMf1rBz5+Dkje+3ADGOpWK2GfJVhG8rueD/0T0gi+Pf/X7c+l5rnHIRBsVxx6vUFiUwiJvk2zjP2ZnPyxa8kGxXVgeC3tm0QIOQDILp7U31g7gsBg44CP+4+DRcwjKdbSNt4CNtP1aF54= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786103371; c=relaxed/simple; bh=IiTixi9C3RoKTu6cZNLff09DrEVQ40crqIsXh+KYYcw=; h=From:To:CC:Subject:Date:Message-ID:MIME-Version:Content-Type; b=AJfGKL+I+Q6lnusDjpKpJsLtJFjY4vjzHqawzH4H8iho4/PcbjKuWxa1bgg3LX4xRiP+S+n9vbpu6NlYbaKTj8lNphAtlpbeFsLArOBZizLQe3A7uTS0k0ARxgBOWqMyg+1dgHAPYU25xxUdKj3VJUnJNhg37Dg5RT2mtXC4rFE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com; spf=pass smtp.mailfrom=huawei.com; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b=cQY4A06x; arc=none smtp.client-ip=113.46.200.218 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=huawei.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huawei.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=huawei.com header.i=@huawei.com header.b="cQY4A06x" dkim-signature: v=1; a=rsa-sha256; d=huawei.com; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From; bh=+lbd/1MNGm4HT0GuWsiCSQDSsz/SkBpL3XmixSXX94E=; b=cQY4A06xoXiuvo2WO1JIxDjiCzCVLBgFgKix3UB+3Jtrl2jto73NrLgC9FIi7hOHM0J0aG9A+ FpRBDrZxOdduEJv5qOMkkqKaNox5OE6bnLevpFFCoQBvR7tQuDx3KLtYIP8uJJaIG3bLraUNzw2 MgN5/29mocUqN7U708FwHPA= Received: from mail.maildlp.com (unknown [172.19.163.104]) by canpmsgout03.his.huawei.com (SkyGuard) with ESMTPS id 4hGhxz5jbtzpSvD; Fri, 7 Aug 2026 19:39:19 +0800 (CST) Received: from kwepemo500018.china.huawei.com (unknown [7.202.195.199]) by mail.maildlp.com (Postfix) with ESMTPS id 364034057F; Fri, 7 Aug 2026 19:48:53 +0800 (CST) Received: from localhost.huawei.com (10.90.31.46) by kwepemo500018.china.huawei.com (7.202.195.199) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.45; Fri, 7 Aug 2026 19:48:52 +0800 From: Jijie Shao To: , , , , , , , , , CC: , , , , , , , Subject: [PATCH v5 net] net: page_pool: fix UAF in __page_pool_release_netmem_dma on xa_cmpxchg race Date: Fri, 7 Aug 2026 19:48:30 +0800 Message-ID: <20260807114830.344336-1-shaojijie@huawei.com> X-Mailer: git-send-email 2.33.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: kwepems500001.china.huawei.com (7.221.188.70) To kwepemo500018.china.huawei.com (7.202.195.199) Content-Type: text/plain; charset="utf-8" This bug was discovered while testing the hns3 driver under channel reconfiguration (`ethtool -L` / `ethtool -G`) with iperf3 traffic on arm64. The race is intermittently triggered when page_pool_destroy() runs page_pool_scrub() concurrently with page return via page_pool_put_netmem() on a different CPU. A WARN in page_pool_clear_pp_info() surfaced the dangling DMA index bits left by the cmpxchg loser, which led to the investigation. page_pool_scrub() iterates pool->dma_mapped via xa_for_each() with no page ref held. __page_pool_release_netmem_dma() currently reads and writes netmem fields (dma_addr, DMA index bits in pp_magic) after xa_cmpxchg() returns. The unref path calls put_page() unconditionally regardless of the cmpxchg outcome; when it loses the cmpxchg, it still frees the page before the scrub winner finishes these netmem accesses, so scrub touches a freed page -- a Use-After-Free. Fix this by splitting the DMA release into two functions: 1. __page_pool_unmap_netmem_dma() caches dma_addr before xa_cmpxchg(), does the cmpxchg to remove the DMA mapping, and calls dma_unmap on the cached address. It never touches netmem fields after the cmpxchg, making it safe for the scrub path which holds no page ref. 2. __page_pool_release_netmem_dma() wraps the above and additionally clears dma_addr and DMA index bits in netmem fields. This is safe only when the caller holds a page ref, so it is used by the return path (page_pool_return_netmem). The scrub path calls __page_pool_unmap_netmem_dma() directly; the return path calls __page_pool_release_netmem_dma(). Fixes: ee62ce7a1d90 ("page_pool: Track DMA-mapped pages and unmap them when= destroying the pool") Suggested-by: Mina Almasry Reviewed-by: Mina Almasry Assisted-by: OhMyOpenCode:GLM-5.2 Signed-off-by: Jijie Shao Reviewed-by: Toke H=C3=B8iland-J=C3=B8rgensen --- Changes in v5: - Replace goto label with if block per Jakub's review. - Add bug discovery context to commit message per Jakub's request. - Add Reviewed-by tag from Mina. - Link to v4: https://lore.kernel.org/r/20260731111507.2355601-1-shaojijie@= huawei.com Changes in v4: - Restructure per Mina's review: merge page_pool_remove_dma_mapping() into __page_pool_unmap_netmem_dma() with dma_unmap inlined via goto label; simplify __page_pool_release_netmem_dma() to a thin wrapper. - Link to v3: https://lore.kernel.org/r/20260729110249.2824835-1-shaojijie@= huawei.com Changes in v3: - Fix unlikely() to likely() for PP_DMA_INDEX_BITS to match file convention. - Link to v2: https://lore.kernel.org/r/20260727132612.3277927-1-shaojijie@= huawei.com Changes in v2: - Redesign the fix per Mina's review: v1's unconditional netmem_set_dma_index() introduced a UAF when the scrub path (no page ref) writes to a page freed by the unref path. - Cache dma_addr before xa_cmpxchg; move dma_addr/DMA index cleanup to page_pool_return_netmem() which holds a page ref. - Rename page_pool_release_dma_index() to page_pool_remove_dma_mapping() to reflect its new role as a pure cmpxchg wrapper. - Link to v1: https://lore.kernel.org/r/20260724092135.414699-1-shaojijie@h= uawei.com --- net/core/page_pool.c | 66 +++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 21dc4a9c8714..50ee550fef73 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -500,29 +500,40 @@ static int page_pool_register_dma_index(struct page_p= ool *pool, return err; } =20 -static int page_pool_release_dma_index(struct page_pool *pool, - netmem_ref netmem) +static void __page_pool_unmap_netmem_dma(struct page_pool *pool, + netmem_ref netmem) { struct page *old, *page =3D netmem_to_page(netmem); unsigned long id; + dma_addr_t dma; =20 - if (unlikely(!PP_DMA_INDEX_BITS)) - return 0; - - id =3D netmem_get_dma_index(netmem); - if (!id) - return -1; + if (!pool->dma_map) + return; =20 - if (in_softirq()) - old =3D xa_cmpxchg(&pool->dma_mapped, id, page, NULL, 0); - else - old =3D xa_cmpxchg_bh(&pool->dma_mapped, id, page, NULL, 0); - if (old !=3D page) - return -1; + /* Cache dma_addr before xa_cmpxchg. The scrub path holds no page ref; + * the unref path calls put_page() regardless of cmpxchg outcome, so + * after the cmpxchg we cannot safely touch netmem fields. + */ + dma =3D page_pool_get_dma_addr_netmem(netmem); =20 - netmem_set_dma_index(netmem, 0); + if (likely(PP_DMA_INDEX_BITS)) { + id =3D netmem_get_dma_index(netmem); + if (!id) + return; + + if (in_softirq()) + old =3D xa_cmpxchg(&pool->dma_mapped, + id, page, NULL, 0); + else + old =3D xa_cmpxchg_bh(&pool->dma_mapped, + id, page, NULL, 0); + if (old !=3D page) + return; + } =20 - return 0; + dma_unmap_page_attrs(pool->p.dev, dma, + PAGE_SIZE << pool->p.order, pool->p.dma_dir, + DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); } =20 static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, g= fp_t gfp) @@ -728,24 +739,16 @@ void page_pool_clear_pp_info(netmem_ref netmem) static __always_inline void __page_pool_release_netmem_dma(struct page_poo= l *pool, netmem_ref netmem) { - dma_addr_t dma; - + /* Caller must hold a page ref: __page_pool_unmap_netmem_dma() is + * safe without a ref, but the field clears below require it. + */ if (!pool->dma_map) - /* Always account for inflight pages, even if we didn't - * map them - */ return; =20 - if (page_pool_release_dma_index(pool, netmem)) - return; - - dma =3D page_pool_get_dma_addr_netmem(netmem); - - /* When page is unmapped, it cannot be returned to our pool */ - dma_unmap_page_attrs(pool->p.dev, dma, - PAGE_SIZE << pool->p.order, pool->p.dma_dir, - DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING); + __page_pool_unmap_netmem_dma(pool, netmem); page_pool_set_dma_addr_netmem(netmem, 0); + if (likely(PP_DMA_INDEX_BITS)) + netmem_set_dma_index(netmem, 0); } =20 /* Disconnects a page (from a page_pool). API users can have a need @@ -1171,8 +1174,9 @@ static void page_pool_scrub(struct page_pool *pool) synchronize_net(); } =20 + /* No page ref, dma-unmap only. */ xa_for_each(&pool->dma_mapped, id, ptr) - __page_pool_release_netmem_dma(pool, page_to_netmem((struct page *)ptr)= ); + __page_pool_unmap_netmem_dma(pool, page_to_netmem((struct page *)ptr)); } =20 /* No more consumers should exist, but producers could still base-commit: 594d905195024b228c962627ae5ae7c17bd582a4 --=20 2.33.0