VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-46086

CVE-2026-46086

Description

In the Linux kernel, the following vulnerability has been resolved:

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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A race condition in Linux kernel's bridge FDB code can cause NULL-pointer dereference via sysfs reads during concurrent local entry updates.

Vulnerability

In the Linux kernel's net/bridge module, 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 RCU readers, such as br_fdb_fillbuf() called through the brforward_read() sysfs path, load f->dst multiple times without synchronization. This allows a concurrent local-FDB update to change f->dst after a NULL check and before the port_no dereference, leading to a NULL-pointer dereference. The affected versions are those prior to the fix commit 0b9e4bbfb7c9 [1].

Exploitation

An attacker with the ability to trigger concurrent bridge FDB updates and sysfs reads (e.g., a local user with CAP_NET_ADMIN) can exploit this race condition. The attacker would need to orchestrate a local FDB entry deletion via fdb_delete_local() while simultaneously reading the FDB entries through the brforward_read() sysfs interface. The race window allows the reader to observe an inconsistent f->dst pointer, leading to a crash.

Impact

Successful exploitation results in a NULL-pointer dereference in the kernel, causing a system crash (denial of service). The vulnerability does not provide privilege escalation or remote code execution based on the available information.

Mitigation

The vulnerability is fixed by commit 0b9e4bbfb7c9 in the Linux kernel [1]. Users should update to a kernel version containing this patch or apply the commit directly. No workaround is documented.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

10
df4601653201

net: bridge: use a stable FDB dst snapshot in RCU readers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhengchuan LiangApr 13, 2026Fixed in 7.1-rc1via kernel-cna
2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index 0c8a06cdd46f18..deb1ab1f24b059 100644
    --- a/net/bridge/br_arp_nd_proxy.c
    +++ b/net/bridge/br_arp_nd_proxy.c
    @@ -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 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index e2c17f620f009a..6eb3ab69a5140f 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -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 && 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;
     		}
    @@ -357,7 +361,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;
     	}
    @@ -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,
    -- 
    cgit 1.3-korg
    
    
    
5424e678f9b3

net: bridge: use a stable FDB dst snapshot in RCU readers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhengchuan LiangApr 13, 2026Fixed in 6.18.27via kernel-cna
2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index 6b5595868a39c0..7ace0f4941bb61 100644
    --- a/net/bridge/br_arp_nd_proxy.c
    +++ b/net/bridge/br_arp_nd_proxy.c
    @@ -202,11 +202,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);
    @@ -470,9 +471,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 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index e2c17f620f009a..6eb3ab69a5140f 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -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 && 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;
     		}
    @@ -357,7 +361,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;
     	}
    @@ -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,
    -- 
    cgit 1.3-korg
    
    
    
9a2d9d4e657b

net: bridge: use a stable FDB dst snapshot in RCU readers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhengchuan LiangApr 13, 2026Fixed in 7.0.4via kernel-cna
2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index 6b5595868a39c0..7ace0f4941bb61 100644
    --- a/net/bridge/br_arp_nd_proxy.c
    +++ b/net/bridge/br_arp_nd_proxy.c
    @@ -202,11 +202,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);
    @@ -470,9 +471,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 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index e2c17f620f009a..6eb3ab69a5140f 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -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 && 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;
     		}
    @@ -357,7 +361,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;
     	}
    @@ -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,
    -- 
    cgit 1.3-korg
    
    
    
0b9e4bbfb7c9

net: bridge: use a stable FDB dst snapshot in RCU readers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhengchuan LiangFixed in 6.6.140via kernel-cna
2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index f033a516756025..985aaf7ff15643 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,
    
  • net/bridge/br_fdb.c+18 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index fa2970db213015..0a51f648c57ece 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -246,6 +246,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;
    @@ -258,8 +259,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;
    @@ -349,7 +353,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;
     		}
    @@ -360,7 +364,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;
     	}
    @@ -790,6 +794,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;
    @@ -806,7 +811,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) {
    @@ -818,8 +824,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)) {
    @@ -940,9 +946,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
    @@ -950,10 +958,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,
    -- 
    cgit 1.3-korg
    
    
    
81af4137a30c

net: bridge: use a stable FDB dst snapshot in RCU readers

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitZhengchuan LiangFixed in 6.12.86via kernel-cna
2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index f033a516756025..985aaf7ff15643 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,
    
  • net/bridge/br_fdb.c+18 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index 9dd405b64fcc9a..39cc761012d489 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,
    -- 
    cgit 1.3-korg
    
    
    
5424e678f9b3

net: bridge: use a stable FDB dst snapshot in RCU readers

2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index 6b5595868a39c0..7ace0f4941bb61 100644
    --- a/net/bridge/br_arp_nd_proxy.c
    +++ b/net/bridge/br_arp_nd_proxy.c
    @@ -202,11 +202,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);
    @@ -470,9 +471,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 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index e2c17f620f009a..6eb3ab69a5140f 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -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 && 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;
     		}
    @@ -357,7 +361,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;
     	}
    @@ -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,
    -- 
    cgit 1.3-korg
    
    
    
9a2d9d4e657b

net: bridge: use a stable FDB dst snapshot in RCU readers

2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index 6b5595868a39c0..7ace0f4941bb61 100644
    --- a/net/bridge/br_arp_nd_proxy.c
    +++ b/net/bridge/br_arp_nd_proxy.c
    @@ -202,11 +202,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);
    @@ -470,9 +471,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 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index e2c17f620f009a..6eb3ab69a5140f 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -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 && 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;
     		}
    @@ -357,7 +361,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;
     	}
    @@ -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,
    -- 
    cgit 1.3-korg
    
    
    
df4601653201

net: bridge: use a stable FDB dst snapshot in RCU readers

2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index 0c8a06cdd46f18..deb1ab1f24b059 100644
    --- a/net/bridge/br_arp_nd_proxy.c
    +++ b/net/bridge/br_arp_nd_proxy.c
    @@ -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 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index e2c17f620f009a..6eb3ab69a5140f 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -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 && 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;
     		}
    @@ -357,7 +361,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;
     	}
    @@ -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,
    -- 
    cgit 1.3-korg
    
    
    
0b9e4bbfb7c9

net: bridge: use a stable FDB dst snapshot in RCU readers

2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index f033a516756025..985aaf7ff15643 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,
    
  • net/bridge/br_fdb.c+18 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index fa2970db213015..0a51f648c57ece 100644
    --- a/net/bridge/br_fdb.c
    +++ b/net/bridge/br_fdb.c
    @@ -246,6 +246,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;
    @@ -258,8 +259,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;
    @@ -349,7 +353,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;
     		}
    @@ -360,7 +364,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;
     	}
    @@ -790,6 +794,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;
    @@ -806,7 +811,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) {
    @@ -818,8 +824,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)) {
    @@ -940,9 +946,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
    @@ -950,10 +958,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,
    -- 
    cgit 1.3-korg
    
    
    
81af4137a30c

net: bridge: use a stable FDB dst snapshot in RCU readers

2 files changed · +23 14
  • net/bridge/br_arp_nd_proxy.c+5 3 modified
    diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
    index f033a516756025..985aaf7ff15643 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,
    
  • net/bridge/br_fdb.c+18 11 modified
    diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
    index 9dd405b64fcc9a..39cc761012d489 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,
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing READ_ONCE/WRITE_ONCE annotations allow a TOCTOU race where a concurrent writer can change f->dst to NULL between a reader's NULL check and its subsequent dereference."

Attack vector

An attacker with the ability to trigger a local FDB entry update (via `fdb_delete_local()`) concurrently with an RCU reader that inspects `f->dst` can cause a NULL-pointer dereference. The reader loads `f->dst` multiple times; a concurrent writer can change `f->dst` to NULL between the reader's NULL check and the subsequent dereference of `f->dst->port_no` or `f->dst->dev`. This is a classic time-of-check-to-time-of-use (TOCTOU) race in RCU-protected data. The attack path is local to the bridge subsystem — no special privileges beyond the ability to trigger bridge configuration changes are required.

Affected code

The vulnerability affects multiple RCU readers in `net/bridge/br_fdb.c` (functions `br_fdb_find_port`, `br_fdb_fillbuf`, and `br_fdb_dump`) and `net/bridge/br_arp_nd_proxy.c` (functions `br_do_proxy_suppress_arp` and `br_do_suppress_nd`). The writer-side bug is in `fdb_delete_local()` in `net/bridge/br_fdb.c`, which updates `f->dst` in-place without proper ordering.

What the fix does

The patch introduces `READ_ONCE()` to take a single snapshot of `f->dst` in each RCU reader (`br_fdb_find_port`, `br_fdb_fillbuf`, `br_fdb_dump`, `br_do_proxy_suppress_arp`, `br_do_suppress_nd`) and uses that snapshot for all subsequent checks and dereferences. On the writer side, `fdb_delete_local()` now uses `WRITE_ONCE()` to publish updates to `f->dst`. This ensures that readers and writers use matching access patterns, preventing the compiler or CPU from reordering or splitting loads, which eliminates the TOCTOU race that could lead to a NULL-pointer dereference.

Preconditions

  • inputThe attacker must be able to trigger a local FDB entry update (via bridge configuration changes that invoke fdb_delete_local) concurrently with an RCU reader accessing the same FDB entry.
  • configThe bridge must have local FDB entries that can be rewritten in-place by fdb_delete_local().

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.