Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: bridge: fix use-after-free due to MST port state bypass

syzbot reported[1] a use-after-free when deleting an expired fdb. It is
due to a race condition between learning still happening and a port being
deleted, after all its fdbs have been flushed. The port's state has been
toggled to disabled so no learning should happen at that time, but if we
have MST enabled, it will bypass the port's state, that together with VLAN
filtering disabled can lead to fdb learning at a time when it shouldn't
happen while the port is being deleted. VLAN filtering must be disabled
because we flush the port VLANs when it's being deleted which will stop
learning. This fix adds a check for the port's vlan group which is
initialized to NULL when the port is getting deleted, that avoids the port
state bypass. When MST is enabled there would be a minimal new overhead
in the fast-path because the port's vlan group pointer is cache-hot.

[1] https://syzkaller.appspot.com/bug?extid=dd280197f0f7ab3917be

Fixes: ec7328b59176 ("net: bridge: mst: Multiple Spanning Tree (MST) mode")
Reported-by: syzbot+dd280197f0f7ab3917be@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/69088ffa.050a0220.29fc44.003d.GAE@google.com/
Signed-off-by: Nikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20251105111919.1499702-2-razor@blackwall.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Nikolay Aleksandrov and committed by
Jakub Kicinski
8dca3697 0216721c

+8 -6
+1 -1
net/bridge/br_forward.c
··· 25 25 26 26 vg = nbp_vlan_group_rcu(p); 27 27 return ((p->flags & BR_HAIRPIN_MODE) || skb->dev != p->dev) && 28 - (br_mst_is_enabled(p->br) || p->state == BR_STATE_FORWARDING) && 28 + (br_mst_is_enabled(p) || p->state == BR_STATE_FORWARDING) && 29 29 br_allowed_egress(vg, skb) && nbp_switchdev_allowed_egress(p, skb) && 30 30 !br_skb_isolated(p, skb); 31 31 }
+2 -2
net/bridge/br_input.c
··· 94 94 95 95 br = p->br; 96 96 97 - if (br_mst_is_enabled(br)) { 97 + if (br_mst_is_enabled(p)) { 98 98 state = BR_STATE_FORWARDING; 99 99 } else { 100 100 if (p->state == BR_STATE_DISABLED) { ··· 429 429 return RX_HANDLER_PASS; 430 430 431 431 forward: 432 - if (br_mst_is_enabled(p->br)) 432 + if (br_mst_is_enabled(p)) 433 433 goto defer_stp_filtering; 434 434 435 435 switch (p->state) {
+5 -3
net/bridge/br_private.h
··· 1935 1935 /* br_mst.c */ 1936 1936 #ifdef CONFIG_BRIDGE_VLAN_FILTERING 1937 1937 DECLARE_STATIC_KEY_FALSE(br_mst_used); 1938 - static inline bool br_mst_is_enabled(struct net_bridge *br) 1938 + static inline bool br_mst_is_enabled(const struct net_bridge_port *p) 1939 1939 { 1940 + /* check the port's vlan group to avoid racing with port deletion */ 1940 1941 return static_branch_unlikely(&br_mst_used) && 1941 - br_opt_get(br, BROPT_MST_ENABLED); 1942 + br_opt_get(p->br, BROPT_MST_ENABLED) && 1943 + rcu_access_pointer(p->vlgrp); 1942 1944 } 1943 1945 1944 1946 int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state, ··· 1955 1953 int br_mst_process(struct net_bridge_port *p, const struct nlattr *mst_attr, 1956 1954 struct netlink_ext_ack *extack); 1957 1955 #else 1958 - static inline bool br_mst_is_enabled(struct net_bridge *br) 1956 + static inline bool br_mst_is_enabled(const struct net_bridge_port *p) 1959 1957 { 1960 1958 return false; 1961 1959 }