From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (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 10B49282F04 for ; Mon, 4 May 2026 07:26:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777879604; cv=none; b=uEdqFAGsiWaL5R0GgL4hMXEUDEi559u0Sjs/x/iBBp1v4Fm5C+IOV0FQzAe2I0IlcRdgFQ6hODfWfl9W4tDLg1eI43j0Z6j+7Hrv8LgUsNqw55rgWvLNNTC0vgDEAfMEwRhFJAGeLS1Mx27PNa1+F+B5rVkBXIaVOOEYm87azT0= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777879604; c=relaxed/simple; bh=dLIjCxDYnIvriS/nA7iMnHRme+wNybXXrLc4RgS94bs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fHxOgrX8YlIlnOfYxwk4/YiUH7mLWsJ7JJX0DXUQtbrq/+xWb1YloO+FhfF0TsTSpduPJY6MuNsyF3joKrEZsRv1Ak4rcAHhaJIO7mTotS2PMnOjerDyjIe/6qVeoDh6bIkzQleETbjlT1q1oQP0JgVkSHpDspECF5+h0WXFAIo= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Tg/V95yi; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Tg/V95yi" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 91DA0C2BCB8; Mon, 4 May 2026 07:26:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1777879603; bh=dLIjCxDYnIvriS/nA7iMnHRme+wNybXXrLc4RgS94bs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Tg/V95yi0+N/EogNoNrXES3s0bhTxuoIuLYKDWnB/dChwWHJp+1LY1KwA5UNRHOul ZMJ4gS0m5wQk+IRJnLCfeEgfa6jJGCeFB0nn1uStjU9eWCpLaTjR5/EHlMXTCwjbsr 7jvK2TS9kotS+818kWp6fMvy5NkkmGelV/lQxGolJS8bTafNpdnZ1RwxJO/awGDooY PCpVB/sMU+ZYASXvjf9lVsJpeN/AMGGJhIE+TDRvUJs6a59DeeYlpmn/jzG/XdQGmY P7yKDmK9faktLoksCTaOvtVnCt8PO3rF9kwUyKOaFiJJYQnxvExEayxmTbuHVarBbV ztMLRBsFkJrzg== From: Sasha Levin To: stable@vger.kernel.org Cc: Zhengchuan Liang , stable@kernel.org, Yifan Wu , Juefei Pu , Yuan Tan , Xin Liu , Ren Wei , Ren Wei , Ido Schimmel , Nikolay Aleksandrov , Paolo Abeni , Sasha Levin Subject: [PATCH 6.12.y] net: bridge: use a stable FDB dst snapshot in RCU readers Date: Mon, 4 May 2026 03:26:37 -0400 Message-ID: <20260504072637.1829085-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <2026050159-dairy-ignore-5a64@gregkh> References: <2026050159-dairy-ignore-5a64@gregkh> Precedence: bulk X-Mailing-List: stable@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Zhengchuan Liang [ Upstream commit df4601653201de21b487c3e7fffd464790cab808 ] Local FDB entries can be rewritten in place by `fdb_delete_local()`, which updates `f->dst` to another port or to `NULL` while keeping the entry alive. Several bridge RCU readers inspect `f->dst`, including `br_fdb_fillbuf()` through the `brforward_read()` sysfs path. These readers currently load `f->dst` multiple times and can therefore observe inconsistent values across the check and later dereference. In `br_fdb_fillbuf()`, this means a concurrent local-FDB update can change `f->dst` after the NULL check and before the `port_no` dereference, leading to a NULL-ptr-deref. Fix this by taking a single `READ_ONCE()` snapshot of `f->dst` in each affected RCU reader and using that snapshot for the rest of the access sequence. Also publish the in-place `f->dst` updates in `fdb_delete_local()` with `WRITE_ONCE()` so the readers and writer use matching access patterns. Fixes: 960b589f86c7 ("bridge: Properly check if local fdb entry can be deleted in br_fdb_change_mac_address") Cc: stable@kernel.org Reported-by: Yifan Wu Reported-by: Juefei Pu Co-developed-by: Yuan Tan Signed-off-by: Yuan Tan Suggested-by: Xin Liu Tested-by: Ren Wei Signed-off-by: Zhengchuan Liang Signed-off-by: Ren Wei Reviewed-by: Ido Schimmel Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/6570fabb85ecadb8baaf019efe856f407711c7b9.1776043229.git.zcliangcn@gmail.com Signed-off-by: Paolo Abeni [ kept `*idx < cb->args[2]` instead of `*idx < ctx->fdb_idx` ] Signed-off-by: Sasha Levin --- net/bridge/br_arp_nd_proxy.c | 8 +++++--- net/bridge/br_fdb.c | 28 ++++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c index f033a51675602..985aaf7ff1564 100644 --- a/net/bridge/br_arp_nd_proxy.c +++ b/net/bridge/br_arp_nd_proxy.c @@ -199,11 +199,12 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br, f = br_fdb_find_rcu(br, n->ha, vid); if (f) { + const struct net_bridge_port *dst = READ_ONCE(f->dst); bool replied = false; if ((p && (p->flags & BR_PROXYARP)) || - (f->dst && (f->dst->flags & BR_PROXYARP_WIFI)) || - br_is_neigh_suppress_enabled(f->dst, vid)) { + (dst && (dst->flags & BR_PROXYARP_WIFI)) || + br_is_neigh_suppress_enabled(dst, vid)) { if (!vid) br_arp_send(br, p, skb->dev, sip, tip, sha, n->ha, sha, 0, 0); @@ -463,9 +464,10 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br, f = br_fdb_find_rcu(br, n->ha, vid); if (f) { + const struct net_bridge_port *dst = READ_ONCE(f->dst); bool replied = false; - if (br_is_neigh_suppress_enabled(f->dst, vid)) { + if (br_is_neigh_suppress_enabled(dst, vid)) { if (vid != 0) br_nd_send(br, p, skb, n, skb->vlan_proto, diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index 9dd405b64fcc9..39cc761012d48 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -243,6 +243,7 @@ struct net_device *br_fdb_find_port(const struct net_device *br_dev, const unsigned char *addr, __u16 vid) { + const struct net_bridge_port *dst; struct net_bridge_fdb_entry *f; struct net_device *dev = NULL; struct net_bridge *br; @@ -255,8 +256,11 @@ struct net_device *br_fdb_find_port(const struct net_device *br_dev, br = netdev_priv(br_dev); rcu_read_lock(); f = br_fdb_find_rcu(br, addr, vid); - if (f && f->dst) - dev = f->dst->dev; + if (f) { + dst = READ_ONCE(f->dst); + if (dst) + dev = dst->dev; + } rcu_read_unlock(); return dev; @@ -353,7 +357,7 @@ static void fdb_delete_local(struct net_bridge *br, vg = nbp_vlan_group(op); if (op != p && ether_addr_equal(op->dev->dev_addr, addr) && (!vid || br_vlan_find(vg, vid))) { - f->dst = op; + WRITE_ONCE(f->dst, op); clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); return; } @@ -364,7 +368,7 @@ static void fdb_delete_local(struct net_bridge *br, /* Maybe bridge device has same hw addr? */ if (p && ether_addr_equal(br->dev->dev_addr, addr) && (!vid || (v && br_vlan_should_use(v)))) { - f->dst = NULL; + WRITE_ONCE(f->dst, NULL); clear_bit(BR_FDB_ADDED_BY_USER, &f->flags); return; } @@ -827,6 +831,7 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr) int br_fdb_fillbuf(struct net_bridge *br, void *buf, unsigned long maxnum, unsigned long skip) { + const struct net_bridge_port *dst; struct net_bridge_fdb_entry *f; struct __fdb_entry *fe = buf; unsigned long delta; @@ -843,7 +848,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf, continue; /* ignore pseudo entry for local MAC address */ - if (!f->dst) + dst = READ_ONCE(f->dst); + if (!dst) continue; if (skip) { @@ -855,8 +861,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf, memcpy(fe->mac_addr, f->key.addr.addr, ETH_ALEN); /* due to ABI compat need to split into hi/lo */ - fe->port_no = f->dst->port_no; - fe->port_hi = f->dst->port_no >> 8; + fe->port_no = dst->port_no; + fe->port_hi = dst->port_no >> 8; fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags); if (!test_bit(BR_FDB_STATIC, &f->flags)) { @@ -981,9 +987,11 @@ int br_fdb_dump(struct sk_buff *skb, rcu_read_lock(); hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) { + const struct net_bridge_port *dst = READ_ONCE(f->dst); + if (*idx < cb->args[2]) goto skip; - if (filter_dev && (!f->dst || f->dst->dev != filter_dev)) { + if (filter_dev && (!dst || dst->dev != filter_dev)) { if (filter_dev != dev) goto skip; /* !f->dst is a special case for bridge @@ -991,10 +999,10 @@ int br_fdb_dump(struct sk_buff *skb, * Therefore need a little more filtering * we only want to dump the !f->dst case */ - if (f->dst) + if (dst) goto skip; } - if (!filter_dev && f->dst) + if (!filter_dev && dst) goto skip; err = fdb_fill_info(skb, br, f, -- 2.53.0