FAILED: patch "[PATCH] net: bridge: use a stable FDB dst snapshot in RCU readers" failed to apply to 6.1-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.1-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.1.y
git checkout FETCH_HEAD
git cherry-pick -x df4601653201de21b487c3e7fffd464790cab808
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026050101-frill-douche-8319@gregkh' --subject-prefix 'PATCH 6.1.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From df4601653201de21b487c3e7fffd464790cab808 Mon Sep 17 00:00:00 2001
From: Zhengchuan Liang <[email protected]>
Date: Mon, 13 Apr 2026 17:08:46 +0800
Subject: [PATCH] net: bridge: use a stable FDB dst snapshot in RCU readers
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.
net/bridge/br_arp_nd_proxy.c +5 -3
@@ -201,11 +201,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 );
@@ -469,9 +470,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,
net/bridge/br_fdb.c +18 -10
@@ -236,6 +236,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;
@@ -248,8 +249,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;
@@ -346,7 +350,7 @@ static void fdb_delete_local(struct net_bridge *br, vg =nbp_vlan_group (op);if (op != p &ðer_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 ; }
@@ -357,7 +361,7 @@ static void fdb_delete_local(struct net_bridge *br,/* Maybe bridge device has same hw addr? */ if (p &ðer_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 ; }
@@ -928,6 +932,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;
@@ -944,7 +949,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) {
@@ -956,8 +962,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)) {
@@ -1083,9 +1089,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 < ctx->fdb_idx) 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
@@ -1093,10 +1101,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,
Sasha Levin
maintainerPATCH 1/1 net: bridge: use a stable FDB dst snapshot in RCU readers
From: Zhengchuan Liang <[email protected]>
[ 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.
--- net/bridge/br_arp_nd_proxy.c | 8 +++++--- net/bridge/br_fdb.c | 28 ++++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-)
net/bridge/br_arp_nd_proxy.c +5 -3
@@ -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_NEIGH_SUPPRESS )))) { + (dst && (dst->flags & (BR_PROXYARP_WIFI | +BR_NEIGH_SUPPRESS )))) {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 (f->dst && (f->dst->flags &BR_NEIGH_SUPPRESS )) { +if (dst && (dst->flags &BR_NEIGH_SUPPRESS )) {if (vid !=0 ) br_nd_send(br, p, skb, n, skb->vlan_proto,
net/bridge/br_fdb.c +18 -10
@@ -239,6 +239,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;
@@ -251,8 +252,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;
@@ -342,7 +346,7 @@ static void fdb_delete_local(struct net_bridge *br, vg =nbp_vlan_group (op);if (op != p &ðer_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 ; }
@@ -353,7 +357,7 @@ static void fdb_delete_local(struct net_bridge *br,/* Maybe bridge device has same hw addr? */ if (p &ðer_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 ; }
@@ -783,6 +787,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;int num =0 ;
@@ -798,7 +803,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) {
@@ -810,8 +816,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))
@@ -924,9 +930,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
@@ -934,10 +942,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,
Signature · 1 line
2.53.0