AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/20 21:30 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "BRIDGE",
    "BRIDGE_VLAN_FILTERING",
    "VLAN_8021Q",
    "BRIDGE_IGMP_SNOOPING",
    "INET"
  ],
  "FocusSymbols": [
    "br_flood",
    "br_flood_vlan",
    "br_vlan_rebuild_port_array",
    "br_handle_vlan",
    "br_is_neigh_suppress_enabled",
    "br_is_neigh_forward_grat_enabled",
    "br_mst_set_state",
    "nbp_vlan_add",
    "nbp_vlan_delete",
    "br_multicast_flood"
  ],
  "KMSANReasoning": "The patch refactors and optimizes packet flooding and forwarding within the Linux bridge VLAN subsystem:\n1. Replaces the scalar `pvid` and `pvid_state` in `struct net_bridge_vlan_group` with an RCU-protected pointer to `struct net_bridge_vlan`.\n2. Introduces `struct net_bridge_vlan_port_array` dynamically allocated on the master VLAN entry to optimize flooding across VLAN member ports.\n3. Groups the destination port and VLAN into an internal stack structure `struct br_fwd_dst` to pass through `br_flood_*` and `__br_forward`.\n4. Updates neighbor suppression functions to accept `struct net_bridge_vlan *` directly.\n\nAssessment against KMSAN vs KASAN:\n- No uninitialized memory usage: All instances of `struct br_fwd_dst` on the stack are explicitly initialized (using designated/zero initializers or direct assignment to all members) before being passed or read.\n- Dynamic allocations: Bridge VLAN objects continue to be allocated via `kzalloc_obj`, and `net_bridge_vlan_port_array` has its `count` and `vlans` array strictly initialized under RTNL lock based on the count of elements in `masterv-\u003eport_vlist`.\n- No user-space info-leaks: The changes do not expose any uninitialized data, padding, or buffers to user space. `br_vlan_get_info` and `br_vlan_notify` continue to populate the same netlink attributes with properly checked values.\n- Any bugs that could arise here (e.g. RCU lifetime/dereferencing, pointer validity, array index bounds) are covered by KASAN and standard bug detectors.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch modifies the Linux Ethernet Bridge subsystem by introducing an RCU-protected port array and list on master VLAN entries to optimize VLAN-aware packet flooding (br_flood, br_flood_vlan). It restructures bridge forwarding paths, changes how PVID and neighbor suppression flags are queried, introduces dynamic memory allocation (kvmalloc/kvfree_rcu), and adds WARN_ON assertions. Because this modifies reachable core networking code accessible via standard virtual network interfaces and netlink commands, fuzzing is warranted to detect potential memory corruption, RCU use-after-free, or invariant violations.",
  "WorthFuzzing": true
}

1/1 2026/09/20 21:30 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d167430449dc7374d66f5cff67b852f548e6a02b\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Sep 20 21:30:13 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c\nindex 9ce8f440e38fa..da15f4d7c1aef 100644\n--- a/net/bridge/br_arp_nd_proxy.c\n+++ b/net/bridge/br_arp_nd_proxy.c\n@@ -40,6 +40,23 @@ void br_recalculate_neigh_suppress_enabled(struct net_bridge *br)\n }\n \n #if IS_ENABLED(CONFIG_INET)\n+static bool\n+br_is_neigh_suppress_enabled_vid(const struct net_bridge_port *p, u16 vid)\n+{\n+\tconst struct net_bridge_vlan *v = NULL;\n+\n+\tif (p \u0026\u0026 vid \u0026\u0026 test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, \u0026p-\u003eflags)) {\n+\t\tstruct net_bridge_vlan_group *vg;\n+\n+\t\tvg = nbp_vlan_group_rcu(p);\n+\t\tv = br_vlan_find(vg, vid);\n+\t\tif (!v)\n+\t\t\treturn false;\n+\t}\n+\n+\treturn br_is_neigh_suppress_enabled(p, v);\n+}\n+\n static void br_arp_send(struct net_bridge *br, struct net_bridge_port *p,\n \t\t\tstruct net_device *dev, __be32 dest_ip, __be32 src_ip,\n \t\t\tconst unsigned char *dest_hw,\n@@ -159,7 +176,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,\n \t\treturn;\n \n \tif (br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) {\n-\t\tif (br_is_neigh_suppress_enabled(p, vid))\n+\t\tif (br_is_neigh_suppress_enabled_vid(p, vid))\n \t\t\treturn;\n \t\tif (is_unicast_ether_addr(eth_hdr(skb)-\u003eh_dest) \u0026\u0026\n \t\t    parp-\u003ear_op == htons(ARPOP_REQUEST))\n@@ -211,7 +228,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,\n \n \t\t\tif ((p \u0026\u0026 test_bit(BR_PROXYARP_BIT, \u0026p-\u003eflags)) ||\n \t\t\t    (dst \u0026\u0026 test_bit(BR_PROXYARP_WIFI_BIT, \u0026dst-\u003eflags)) ||\n-\t\t\t    br_is_neigh_suppress_enabled(dst, vid)) {\n+\t\t\t    br_is_neigh_suppress_enabled_vid(dst, vid)) {\n \t\t\t\tif (!vid)\n \t\t\t\t\tbr_arp_send(br, p, skb-\u003edev, sip, tip,\n \t\t\t\t\t\t    sha, ha, sha, 0, 0);\n@@ -424,7 +441,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,\n \tBR_INPUT_SKB_CB(skb)-\u003eproxyarp_replied = 0;\n \tBR_INPUT_SKB_CB(skb)-\u003egrat_arp = 0;\n \n-\tif (br_is_neigh_suppress_enabled(p, vid))\n+\tif (br_is_neigh_suppress_enabled_vid(p, vid))\n \t\treturn;\n \n \tif (is_unicast_ether_addr(eth_hdr(skb)-\u003eh_dest) \u0026\u0026\n@@ -486,7 +503,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,\n \t\t\tconst struct net_bridge_port *dst = READ_ONCE(f-\u003edst);\n \t\t\tbool replied = false;\n \n-\t\t\tif (br_is_neigh_suppress_enabled(dst, vid)) {\n+\t\t\tif (br_is_neigh_suppress_enabled_vid(dst, vid)) {\n \t\t\t\tif (vid != 0)\n \t\t\t\t\tbr_nd_send(br, p, skb, n, ha,\n \t\t\t\t\t\t   skb-\u003evlan_proto,\n@@ -509,35 +526,25 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,\n }\n #endif\n \n-bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid)\n+bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p,\n+\t\t\t\t  const struct net_bridge_vlan *v)\n {\n \tif (!p)\n \t\treturn false;\n \n-\tif (vid \u0026\u0026 test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, \u0026p-\u003eflags)) {\n-\t\tstruct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p);\n-\t\tstruct net_bridge_vlan *v;\n-\n-\t\tv = br_vlan_find(vg, vid);\n-\t\tif (!v)\n-\t\t\treturn false;\n+\tif (v \u0026\u0026 test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, \u0026p-\u003eflags))\n \t\treturn !!(READ_ONCE(v-\u003epriv_flags) \u0026\n \t\t\t  BR_VLFLAG_NEIGH_SUPPRESS_ENABLED);\n-\t}\n+\n \treturn test_bit(BR_NEIGH_SUPPRESS_BIT, \u0026p-\u003eflags);\n }\n \n-bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid)\n+bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p,\n+\t\t\t\t      const struct net_bridge_vlan *v)\n {\n-\tif (vid \u0026\u0026 test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, \u0026p-\u003eflags)) {\n-\t\tstruct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p);\n-\t\tstruct net_bridge_vlan *v;\n-\n-\t\tv = br_vlan_find(vg, vid);\n-\t\tif (!v)\n-\t\t\treturn false;\n+\tif (v \u0026\u0026 test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, \u0026p-\u003eflags))\n \t\treturn !!(READ_ONCE(v-\u003epriv_flags) \u0026\n \t\t\t  BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED);\n-\t}\n+\n \treturn test_bit(BR_NEIGH_FORWARD_GRAT_BIT, \u0026p-\u003eflags);\n }\ndiff --git a/net/bridge/br_device.c b/net/bridge/br_device.c\nindex e01c44a90d84b..ce9ea9ac3d0ad 100644\n--- a/net/bridge/br_device.c\n+++ b/net/bridge/br_device.c\n@@ -89,10 +89,10 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n \n \tdest = eth_hdr(skb)-\u003eh_dest;\n \tif (is_broadcast_ether_addr(dest)) {\n-\t\tbr_flood(br, skb, BR_PKT_BROADCAST, false, true, vid);\n+\t\tbr_flood(br, vlan, skb, BR_PKT_BROADCAST, false, true);\n \t} else if (is_multicast_ether_addr(dest)) {\n \t\tif (unlikely(netpoll_tx_running(dev))) {\n-\t\t\tbr_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);\n+\t\t\tbr_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);\n \t\t\tgoto out;\n \t\t}\n \t\tif (br_multicast_rcv(\u0026brmctx, \u0026pmctx_null, vlan, skb, vid)) {\n@@ -105,11 +105,11 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n \t\t    br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst))\n \t\t\tbr_multicast_flood(mdst, skb, brmctx, false, true);\n \t\telse\n-\t\t\tbr_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);\n+\t\t\tbr_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);\n \t} else if ((dst = br_fdb_find_rcu(br, dest, vid)) != NULL) {\n \t\tbr_forward(READ_ONCE(dst-\u003edst), skb, false, true);\n \t} else {\n-\t\tbr_flood(br, skb, BR_PKT_UNICAST, false, true, vid);\n+\t\tbr_flood(br, vlan, skb, BR_PKT_UNICAST, false, true);\n \t}\n out:\n \trcu_read_unlock();\ndiff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c\nindex 46c762ca5177e..b5eece1ff9e8c 100644\n--- a/net/bridge/br_forward.c\n+++ b/net/bridge/br_forward.c\n@@ -7,7 +7,6 @@\n  *\tLennert Buytenhek\t\t\u003cbuytenh@gnu.org\u003e\n  */\n \n-#include \u003clinux/err.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/kernel.h\u003e\n #include \u003clinux/netdevice.h\u003e\n@@ -17,16 +16,31 @@\n #include \u003clinux/netfilter_bridge.h\u003e\n #include \"br_private.h\"\n \n+struct br_fwd_dst {\n+\tconst struct net_bridge_port *port;\n+\tstruct net_bridge_vlan *vlan;\n+};\n+\n+static bool should_deliver_vlan(const struct br_fwd_dst *fwd,\n+\t\t\t\tconst struct sk_buff *skb)\n+{\n+\tif (fwd-\u003evlan)\n+\t\treturn br_vlan_state_allowed(br_vlan_get_state(fwd-\u003evlan),\n+\t\t\t\t\t     false);\n+\n+\treturn br_allowed_egress(nbp_vlan_group_rcu(fwd-\u003eport), skb);\n+}\n+\n /* Don't forward packets to originating port or forwarding disabled */\n-static inline int should_deliver(const struct net_bridge_port *p,\n-\t\t\t\t const struct sk_buff *skb)\n+static __always_inline bool should_deliver(const struct br_fwd_dst *fwd,\n+\t\t\t\t\t   const struct sk_buff *skb)\n {\n-\tstruct net_bridge_vlan_group *vg;\n+\tconst struct net_bridge_port *p = fwd-\u003eport;\n \n-\tvg = nbp_vlan_group_rcu(p);\n \treturn (test_bit(BR_HAIRPIN_MODE_BIT, \u0026p-\u003eflags) || skb-\u003edev != p-\u003edev) \u0026\u0026\n \t\t(br_mst_is_enabled(p) || p-\u003estate == BR_STATE_FORWARDING) \u0026\u0026\n-\t\tbr_allowed_egress(vg, skb) \u0026\u0026 nbp_switchdev_allowed_egress(p, skb) \u0026\u0026\n+\t\tshould_deliver_vlan(fwd, skb) \u0026\u0026\n+\t\tnbp_switchdev_allowed_egress(p, skb) \u0026\u0026\n \t\t!br_skb_isolated(p, skb);\n }\n \n@@ -70,9 +84,10 @@ int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)\n }\n EXPORT_SYMBOL_GPL(br_forward_finish);\n \n-static void __br_forward(const struct net_bridge_port *to,\n+static void __br_forward(const struct br_fwd_dst *fwd,\n \t\t\t struct sk_buff *skb, bool local_orig)\n {\n+\tconst struct net_bridge_port *to = fwd-\u003eport;\n \tstruct net_bridge_vlan_group *vg;\n \tstruct net_device *indev;\n \tstruct net *net;\n@@ -83,8 +98,8 @@ static void __br_forward(const struct net_bridge_port *to,\n \t */\n \tnbp_switchdev_frame_mark_tx_fwd_offload(to, skb);\n \n-\tvg = nbp_vlan_group_rcu(to);\n-\tskb = br_handle_vlan(to-\u003ebr, to, vg, skb);\n+\tvg = fwd-\u003evlan ? NULL : nbp_vlan_group_rcu(to);\n+\tskb = br_handle_vlan(to-\u003ebr, to, vg, fwd-\u003evlan, skb);\n \tif (!skb)\n \t\treturn;\n \n@@ -117,7 +132,7 @@ static void __br_forward(const struct net_bridge_port *to,\n \t\tbr_forward_finish);\n }\n \n-static int deliver_clone(const struct net_bridge_port *prev,\n+static int deliver_clone(const struct br_fwd_dst *fwd,\n \t\t\t struct sk_buff *skb, bool local_orig)\n {\n \tstruct net_device *dev = BR_INPUT_SKB_CB(skb)-\u003ebrdev;\n@@ -128,7 +143,7 @@ static int deliver_clone(const struct net_bridge_port *prev,\n \t\treturn -ENOMEM;\n \t}\n \n-\t__br_forward(prev, skb, local_orig);\n+\t__br_forward(fwd, skb, local_orig);\n \treturn 0;\n }\n \n@@ -144,6 +159,8 @@ static int deliver_clone(const struct net_bridge_port *prev,\n void br_forward(const struct net_bridge_port *to,\n \t\tstruct sk_buff *skb, bool local_rcv, bool local_orig)\n {\n+\tstruct br_fwd_dst fwd;\n+\n \tif (unlikely(!to))\n \t\tgoto out;\n \n@@ -159,11 +176,13 @@ void br_forward(const struct net_bridge_port *to,\n \t\tto = backup_port;\n \t}\n \n-\tif (should_deliver(to, skb)) {\n+\tfwd.port = to;\n+\tfwd.vlan = NULL;\n+\tif (should_deliver(\u0026fwd, skb)) {\n \t\tif (local_rcv)\n-\t\t\tdeliver_clone(to, skb, local_orig);\n+\t\t\tdeliver_clone(\u0026fwd, skb, local_orig);\n \t\telse\n-\t\t\t__br_forward(to, skb, local_orig);\n+\t\t\t__br_forward(\u0026fwd, skb, local_orig);\n \t\treturn;\n \t}\n \n@@ -173,95 +192,162 @@ void br_forward(const struct net_bridge_port *to,\n }\n EXPORT_SYMBOL_GPL(br_forward);\n \n-static struct net_bridge_port *maybe_deliver(\n-\tstruct net_bridge_port *prev, struct net_bridge_port *p,\n-\tstruct sk_buff *skb, bool local_orig)\n+static int maybe_deliver(struct br_fwd_dst *prev, const struct br_fwd_dst *fwd,\n+\t\t\t struct sk_buff *skb, bool local_orig)\n {\n+\tconst struct net_bridge_port *p = fwd-\u003eport;\n \tu8 igmp_type = br_multicast_igmp_type(skb);\n \tint err;\n \n-\tif (!should_deliver(p, skb))\n-\t\treturn prev;\n+\tif (!should_deliver(fwd, skb))\n+\t\treturn 0;\n \n \tnbp_switchdev_frame_mark_tx_fwd_to_hwdom(p, skb);\n \n-\tif (!prev)\n+\tif (!prev-\u003eport)\n \t\tgoto out;\n \n \terr = deliver_clone(prev, skb, local_orig);\n \tif (err)\n-\t\treturn ERR_PTR(err);\n+\t\treturn err;\n out:\n \tbr_multicast_count(p-\u003ebr, p, skb, igmp_type, BR_MCAST_DIR_TX);\n+\t*prev = *fwd;\n \n-\treturn p;\n+\treturn 0;\n }\n \n-/* called under rcu_read_lock */\n-void br_flood(struct net_bridge *br, struct sk_buff *skb,\n-\t      enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,\n-\t      u16 vid)\n+static void br_flood_finish(const struct br_fwd_dst *fwd, int err,\n+\t\t\t    struct sk_buff *skb,\n+\t\t\t    bool local_rcv, bool local_orig)\n {\n \tenum skb_drop_reason reason = SKB_DROP_REASON_NO_TX_TARGET;\n-\tstruct net_bridge_port *prev = NULL;\n-\tstruct net_bridge_port *p;\n \n-\tbr_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);\n+\tif (err || !fwd-\u003eport) {\n+\t\tif (err)\n+\t\t\treason = err == -ENOMEM ? SKB_DROP_REASON_NOMEM :\n+\t\t\t\t\t\t  SKB_DROP_REASON_NOT_SPECIFIED;\n+\n+\t\tif (!local_rcv)\n+\t\t\tkfree_skb_reason(skb, reason);\n+\t\treturn;\n+\t}\n \n-\tlist_for_each_entry_rcu(p, \u0026br-\u003eport_list, list) {\n-\t\t/* Do not flood unicast traffic to ports that turn it off, nor\n-\t\t * other traffic if flood off, except for traffic we originate\n+\tif (local_rcv)\n+\t\tdeliver_clone(fwd, skb, local_orig);\n+\telse\n+\t\t__br_forward(fwd, skb, local_orig);\n+}\n+\n+static int br_flood_port(struct br_fwd_dst *prev,\n+\t\t\t const struct br_fwd_dst *fwd, struct sk_buff *skb,\n+\t\t\t enum br_pkt_type pkt_type, bool local_orig)\n+{\n+\tconst struct net_bridge_port *p = fwd-\u003eport;\n+\n+\t/* Do not flood unicast traffic to ports that turn it off, nor\n+\t * other traffic if flood off, except for traffic we originate\n+\t */\n+\tswitch (pkt_type) {\n+\tcase BR_PKT_UNICAST:\n+\t\tif (!test_bit(BR_FLOOD_BIT, \u0026p-\u003eflags))\n+\t\t\treturn 0;\n+\t\tbreak;\n+\tcase BR_PKT_MULTICAST:\n+\t\tif (!test_bit(BR_MCAST_FLOOD_BIT, \u0026p-\u003eflags) \u0026\u0026\n+\t\t    skb-\u003edev != p-\u003ebr-\u003edev)\n+\t\t\treturn 0;\n+\t\tbreak;\n+\tcase BR_PKT_BROADCAST:\n+\t\tif (!test_bit(BR_BCAST_FLOOD_BIT, \u0026p-\u003eflags) \u0026\u0026\n+\t\t    skb-\u003edev != p-\u003ebr-\u003edev)\n+\t\t\treturn 0;\n+\t\tbreak;\n+\t}\n+\n+\t/* Do not flood to ports that enable proxy ARP */\n+\tif (test_bit(BR_PROXYARP_BIT, \u0026p-\u003eflags))\n+\t\treturn 0;\n+\tif (BR_INPUT_SKB_CB(skb)-\u003eproxyarp_replied) {\n+\t\tif (test_bit(BR_PROXYARP_WIFI_BIT, \u0026p-\u003eflags))\n+\t\t\treturn 0;\n+\t\t/* For gratuitous ARPs/NAs, check neigh_forward_grat.\n+\t\t * For regular ARPs/NDs, check only neigh_suppress.\n \t\t */\n-\t\tswitch (pkt_type) {\n-\t\tcase BR_PKT_UNICAST:\n-\t\t\tif (!test_bit(BR_FLOOD_BIT, \u0026p-\u003eflags))\n-\t\t\t\tcontinue;\n-\t\t\tbreak;\n-\t\tcase BR_PKT_MULTICAST:\n-\t\t\tif (!test_bit(BR_MCAST_FLOOD_BIT, \u0026p-\u003eflags) \u0026\u0026 skb-\u003edev != br-\u003edev)\n-\t\t\t\tcontinue;\n-\t\t\tbreak;\n-\t\tcase BR_PKT_BROADCAST:\n-\t\t\tif (!test_bit(BR_BCAST_FLOOD_BIT, \u0026p-\u003eflags) \u0026\u0026 skb-\u003edev != br-\u003edev)\n-\t\t\t\tcontinue;\n-\t\t\tbreak;\n-\t\t}\n+\t\tif (br_is_neigh_suppress_enabled(p, fwd-\u003evlan) \u0026\u0026\n+\t\t    (!BR_INPUT_SKB_CB(skb)-\u003egrat_arp ||\n+\t\t     !br_is_neigh_forward_grat_enabled(p, fwd-\u003evlan)))\n+\t\t\treturn 0;\n+\t}\n \n-\t\t/* Do not flood to ports that enable proxy ARP */\n-\t\tif (test_bit(BR_PROXYARP_BIT, \u0026p-\u003eflags))\n-\t\t\tcontinue;\n-\t\tif (BR_INPUT_SKB_CB(skb)-\u003eproxyarp_replied) {\n-\t\t\tif (test_bit(BR_PROXYARP_WIFI_BIT, \u0026p-\u003eflags))\n-\t\t\t\tcontinue;\n-\t\t\t/* For gratuitous ARPs/NAs, check neigh_forward_grat.\n-\t\t\t * For regular ARPs/NDs, check only neigh_suppress.\n-\t\t\t */\n-\t\t\tif (br_is_neigh_suppress_enabled(p, vid) \u0026\u0026\n-\t\t\t    (!BR_INPUT_SKB_CB(skb)-\u003egrat_arp ||\n-\t\t\t     !br_is_neigh_forward_grat_enabled(p, vid)))\n-\t\t\t\tcontinue;\n-\t\t}\n+\treturn maybe_deliver(prev, fwd, skb, local_orig);\n+}\n \n-\t\tprev = maybe_deliver(prev, p, skb, local_orig);\n-\t\tif (IS_ERR(prev)) {\n-\t\t\treason = PTR_ERR(prev) == -ENOMEM ? SKB_DROP_REASON_NOMEM :\n-\t\t\t\t SKB_DROP_REASON_NOT_SPECIFIED;\n-\t\t\tgoto out;\n+static int br_flood_vlan(struct br_fwd_dst *prev,\n+\t\t\t struct net_bridge_vlan *v, struct sk_buff *skb,\n+\t\t\t enum br_pkt_type pkt_type, bool local_orig)\n+{\n+\tstruct net_bridge_vlan_port_array *array;\n+\tstruct net_bridge_vlan *masterv, *pv;\n+\tstruct br_fwd_dst dst;\n+\tint err;\n+\n+\tmasterv = br_vlan_is_master(v) ? v : v-\u003ebrvlan;\n+\tarray = rcu_dereference(masterv-\u003eport_array);\n+\tif (array) {\n+\t\tunsigned int i;\n+\n+\t\tfor (i = 0; i \u003c array-\u003ecount; i++) {\n+\t\t\tpv = array-\u003evlans[i];\n+\t\t\tdst.port = pv-\u003eport;\n+\t\t\tdst.vlan = pv;\n+\t\t\terr = br_flood_port(prev, \u0026dst, skb, pkt_type,\n+\t\t\t\t\t    local_orig);\n+\t\t\tif (err)\n+\t\t\t\treturn err;\n+\t\t}\n+\t} else {\n+\t\tlist_for_each_entry_rcu(pv, \u0026masterv-\u003eport_vlist, port_vlist) {\n+\t\t\tdst.port = pv-\u003eport;\n+\t\t\tdst.vlan = pv;\n+\t\t\terr = br_flood_port(prev, \u0026dst, skb, pkt_type,\n+\t\t\t\t\t    local_orig);\n+\t\t\tif (err)\n+\t\t\t\treturn err;\n \t\t}\n \t}\n \n-\tif (!prev)\n-\t\tgoto out;\n+\treturn 0;\n+}\n \n-\tif (local_rcv)\n-\t\tdeliver_clone(prev, skb, local_orig);\n-\telse\n-\t\t__br_forward(prev, skb, local_orig);\n-\treturn;\n+/* called under rcu_read_lock */\n+void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,\n+\t      struct sk_buff *skb, enum br_pkt_type pkt_type,\n+\t      bool local_rcv, bool local_orig)\n+{\n+\tstruct br_fwd_dst prev = {};\n+\tint err = 0;\n \n-out:\n-\tif (!local_rcv)\n-\t\tkfree_skb_reason(skb, reason);\n+\tbr_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);\n+\n+\tif (v) {\n+\t\terr = br_flood_vlan(\u0026prev, v, skb, pkt_type, local_orig);\n+\t} else {\n+\t\tstruct net_bridge_port *p;\n+\n+\t\tlist_for_each_entry_rcu(p, \u0026br-\u003eport_list, list) {\n+\t\t\tstruct br_fwd_dst fwd = {\n+\t\t\t\t.port = p,\n+\t\t\t};\n+\n+\t\t\terr = br_flood_port(\u0026prev, \u0026fwd, skb, pkt_type,\n+\t\t\t\t\t    local_orig);\n+\t\t\tif (err)\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tbr_flood_finish(\u0026prev, err, skb, local_rcv, local_orig);\n }\n \n #ifdef CONFIG_BRIDGE_IGMP_SNOOPING\n@@ -270,9 +356,12 @@ static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,\n {\n \tstruct net_device *dev = BR_INPUT_SKB_CB(skb)-\u003ebrdev;\n \tconst unsigned char *src = eth_hdr(skb)-\u003eh_source;\n+\tstruct br_fwd_dst fwd = {\n+\t\t.port = p,\n+\t};\n \tstruct sk_buff *nskb;\n \n-\tif (!should_deliver(p, skb))\n+\tif (!should_deliver(\u0026fwd, skb))\n \t\treturn;\n \n \t/* Even with hairpin, no soliloquies - prevent breaking IPv6 DAD */\n@@ -292,7 +381,7 @@ static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,\n \tif (!is_broadcast_ether_addr(addr))\n \t\tmemcpy(eth_hdr(skb)-\u003eh_dest, addr, ETH_ALEN);\n \n-\t__br_forward(p, skb, local_orig);\n+\t__br_forward(\u0026fwd, skb, local_orig);\n }\n \n /* called with rcu_read_lock */\n@@ -301,11 +390,11 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,\n \t\t\tstruct net_bridge_mcast *brmctx,\n \t\t\tbool local_rcv, bool local_orig)\n {\n-\tenum skb_drop_reason reason = SKB_DROP_REASON_NO_TX_TARGET;\n-\tstruct net_bridge_port *prev = NULL;\n \tstruct net_bridge_port_group *p;\n \tbool allow_mode_include = true;\n+\tstruct br_fwd_dst prev = {};\n \tstruct hlist_node *rp;\n+\tint err = 0;\n \n \trp = br_multicast_get_first_rport_node(brmctx, skb);\n \n@@ -321,6 +410,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,\n \n \twhile (p || rp) {\n \t\tstruct net_bridge_port *port, *lport, *rport;\n+\t\tstruct br_fwd_dst fwd = {};\n \n \t\tlport = p ? p-\u003ekey.port : NULL;\n \t\trport = br_multicast_rport_from_node_skb(rp, skb);\n@@ -342,12 +432,11 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,\n \t\t\tport = rport;\n \t\t}\n \n-\t\tprev = maybe_deliver(prev, port, skb, local_orig);\n-\t\tif (IS_ERR(prev)) {\n-\t\t\treason = PTR_ERR(prev) == -ENOMEM ? SKB_DROP_REASON_NOMEM :\n-\t\t\t\t SKB_DROP_REASON_NOT_SPECIFIED;\n-\t\t\tgoto out;\n-\t\t}\n+\t\tfwd.port = port;\n+\t\terr = maybe_deliver(\u0026prev, \u0026fwd, skb, local_orig);\n+\t\tif (err)\n+\t\t\tbreak;\n+\n delivered:\n \t\tif ((unsigned long)lport \u003e= (unsigned long)port)\n \t\t\tp = rcu_dereference(p-\u003enext);\n@@ -355,17 +444,6 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,\n \t\t\trp = rcu_dereference(hlist_next_rcu(rp));\n \t}\n \n-\tif (!prev)\n-\t\tgoto out;\n-\n-\tif (local_rcv)\n-\t\tdeliver_clone(prev, skb, local_orig);\n-\telse\n-\t\t__br_forward(prev, skb, local_orig);\n-\treturn;\n-\n-out:\n-\tif (!local_rcv)\n-\t\tkfree_skb_reason(skb, reason);\n+\tbr_flood_finish(\u0026prev, err, skb, local_rcv, local_orig);\n }\n #endif\ndiff --git a/net/bridge/br_input.c b/net/bridge/br_input.c\nindex 8bed72baf1615..4357d78524a68 100644\n--- a/net/bridge/br_input.c\n+++ b/net/bridge/br_input.c\n@@ -58,7 +58,7 @@ static int br_pass_frame_up(struct sk_buff *skb, bool promisc)\n \n \tindev = skb-\u003edev;\n \tskb-\u003edev = brdev;\n-\tskb = br_handle_vlan(br, NULL, vg, skb);\n+\tskb = br_handle_vlan(br, NULL, vg, NULL, skb);\n \tif (!skb)\n \t\treturn NET_RX_DROP;\n \t/* update the multicast stats if the packet is IGMP/MLD */\n@@ -226,7 +226,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb\n \t\tbr_forward(READ_ONCE(dst-\u003edst), skb, local_rcv, false);\n \t} else {\n \t\tif (!mcast_hit)\n-\t\t\tbr_flood(br, skb, pkt_type, local_rcv, false, vid);\n+\t\t\tbr_flood(br, vlan, skb, pkt_type, local_rcv, false);\n \t\telse\n \t\t\tbr_multicast_flood(mdst, skb, brmctx, local_rcv, false);\n \t}\ndiff --git a/net/bridge/br_mst.c b/net/bridge/br_mst.c\nindex 63fc1dd0e9bc0..252d5c927f716 100644\n--- a/net/bridge/br_mst.c\n+++ b/net/bridge/br_mst.c\n@@ -79,16 +79,11 @@ int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state)\n }\n EXPORT_SYMBOL_GPL(br_mst_get_state);\n \n-static void br_mst_vlan_set_state(struct net_bridge_vlan_group *vg,\n-\t\t\t\t  struct net_bridge_vlan *v,\n-\t\t\t\t  u8 state)\n+static void br_mst_vlan_set_state(struct net_bridge_vlan *v, u8 state)\n {\n \tif (br_vlan_get_state(v) == state)\n \t\treturn;\n \n-\tif (v-\u003evid == br_get_pvid(vg))\n-\t\tbr_vlan_set_pvid_state(vg, state);\n-\n \tbr_vlan_set_state(v, state);\n }\n \n@@ -129,7 +124,7 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state,\n \t\tif (READ_ONCE(v-\u003ebrvlan-\u003emsti) != msti)\n \t\t\tcontinue;\n \n-\t\tbr_mst_vlan_set_state(vg, v, state);\n+\t\tbr_mst_vlan_set_state(v, state);\n \t}\n \n out_rcu_unlock:\n@@ -149,13 +144,13 @@ static void br_mst_vlan_sync_state(struct net_bridge_vlan *pv, u16 msti)\n \t\t * it.\n \t\t */\n \t\tif (v != pv \u0026\u0026 v-\u003ebrvlan-\u003emsti == msti) {\n-\t\t\tbr_mst_vlan_set_state(vg, pv, br_vlan_get_state(v));\n+\t\t\tbr_mst_vlan_set_state(pv, br_vlan_get_state(v));\n \t\t\treturn;\n \t\t}\n \t}\n \n \t/* Otherwise, start out in a new MSTI with all ports disabled. */\n-\treturn br_mst_vlan_set_state(vg, pv, BR_STATE_DISABLED);\n+\treturn br_mst_vlan_set_state(pv, BR_STATE_DISABLED);\n }\n \n int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti)\ndiff --git a/net/bridge/br_private.h b/net/bridge/br_private.h\nindex 09c397e303307..67117fb3dc880 100644\n--- a/net/bridge/br_private.h\n+++ b/net/bridge/br_private.h\n@@ -190,6 +190,17 @@ enum {\n \tBR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED = BIT(6),\n };\n \n+/* start publishing arrays when there're \u003e BR_VLAN_PORT_ARRAY_THRESHOLD\n+ * port-VLANs\n+ */\n+#define BR_VLAN_PORT_ARRAY_THRESHOLD 8\n+\n+struct net_bridge_vlan_port_array {\n+\tstruct rcu_head\t\trcu;\n+\tunsigned int\t\tcount;\n+\tstruct net_bridge_vlan\t*vlans[];\n+};\n+\n /**\n  * struct net_bridge_vlan - per-vlan entry\n  *\n@@ -210,6 +221,8 @@ enum {\n  * @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast\n  *                  context\n  * @msti: if MASTER flag set, this holds the VLANs MST instance\n+ * @port_array: if MASTER flag set, this is the port-VLAN array\n+ * @port_vlist: if MASTER flag set, this is the port-VLAN list\n  * @vlist: sorted list of VLAN entries\n  * @rcu: used for entry destruction\n  *\n@@ -244,6 +257,8 @@ struct net_bridge_vlan {\n \n \tu16\t\t\t\tmsti;\n \n+\tstruct net_bridge_vlan_port_array __rcu *port_array;\n+\tstruct list_head\t\tport_vlist;\n \tstruct list_head\t\tvlist;\n \n \tstruct rcu_head\t\t\trcu;\n@@ -256,8 +271,7 @@ struct net_bridge_vlan {\n  * @tunnel_hash: Hash table to map from tunnel key ID (e.g. VXLAN VNI) to VLAN\n  * @vlan_list: sorted VLAN entry list\n  * @num_vlans: number of total VLAN entries\n- * @pvid: PVID VLAN id\n- * @pvid_state: PVID's STP state (e.g. forwarding, learning, blocking)\n+ * @pvid: RCU-protected PVID VLAN entry\n  *\n  * IMPORTANT: Be careful when checking if there're VLAN entries using list\n  *            primitives because the bridge can have entries in its list which\n@@ -269,9 +283,8 @@ struct net_bridge_vlan_group {\n \tstruct rhashtable\t\tvlan_hash;\n \tstruct rhashtable\t\ttunnel_hash;\n \tstruct list_head\t\tvlan_list;\n+\tstruct net_bridge_vlan\t\t__rcu *pvid;\n \tu16\t\t\t\tnum_vlans;\n-\tu16\t\t\t\tpvid;\n-\tu8\t\t\t\tpvid_state;\n };\n \n /* bridge fdb flags */\n@@ -912,9 +925,9 @@ int br_dev_queue_push_xmit(struct net *net, struct sock *sk, struct sk_buff *skb\n void br_forward(const struct net_bridge_port *to, struct sk_buff *skb,\n \t\tbool local_rcv, bool local_orig);\n int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb);\n-void br_flood(struct net_bridge *br, struct sk_buff *skb,\n-\t      enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,\n-\t      u16 vid);\n+void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,\n+\t      struct sk_buff *skb, enum br_pkt_type pkt_type,\n+\t      bool local_rcv, bool local_orig);\n \n /* return true if both source port and dest port are isolated */\n static inline bool br_skb_isolated(const struct net_bridge_port *to,\n@@ -1593,6 +1606,7 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid);\n struct sk_buff *br_handle_vlan(struct net_bridge *br,\n \t\t\t       const struct net_bridge_port *port,\n \t\t\t       struct net_bridge_vlan_group *vg,\n+\t\t\t       struct net_bridge_vlan *vlan,\n \t\t\t       struct sk_buff *skb);\n int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags,\n \t\tbool *changed, struct netlink_ext_ack *extack);\n@@ -1687,10 +1701,16 @@ static inline int br_vlan_get_tag(const struct sk_buff *skb, u16 *vid)\n \n static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)\n {\n+\tstruct net_bridge_vlan *pvid;\n+\n \tif (!vg)\n \t\treturn 0;\n \n-\treturn READ_ONCE(vg-\u003epvid);\n+\tpvid = rcu_dereference_rtnl(vg-\u003epvid);\n+\tif (!pvid)\n+\t\treturn 0;\n+\n+\treturn pvid-\u003evid;\n }\n \n static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)\n@@ -1724,6 +1744,7 @@ static inline bool br_should_learn(struct net_bridge_port *p,\n static inline struct sk_buff *br_handle_vlan(struct net_bridge *br,\n \t\t\t\t\t     const struct net_bridge_port *port,\n \t\t\t\t\t     struct net_bridge_vlan_group *vg,\n+\t\t\t\t\t     struct net_bridge_vlan *vlan,\n \t\t\t\t\t     struct sk_buff *skb)\n {\n \treturn skb;\n@@ -1922,17 +1943,6 @@ static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)\n \tbr_multicast_update_vlan_mcast_ctx(v, state);\n }\n \n-static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)\n-{\n-\treturn READ_ONCE(vg-\u003epvid_state);\n-}\n-\n-static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,\n-\t\t\t\t\t  u8 state)\n-{\n-\tWRITE_ONCE(vg-\u003epvid_state, state);\n-}\n-\n /* learn_allow is true at ingress and false at egress */\n static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)\n {\n@@ -1945,6 +1955,11 @@ static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)\n \t\treturn false;\n \t}\n }\n+#else\n+static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)\n+{\n+\treturn false;\n+}\n #endif\n \n /* br_mst.c */\n@@ -2375,6 +2390,8 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,\n void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,\n \t\t       u16 vid, struct net_bridge_port *p, struct nd_msg *msg);\n struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb);\n-bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid);\n-bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid);\n+bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p,\n+\t\t\t\t  const struct net_bridge_vlan *v);\n+bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p,\n+\t\t\t\t      const struct net_bridge_vlan *v);\n #endif\ndiff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c\nindex 34ede0f3e4523..ce5aa15c45402 100644\n--- a/net/bridge/br_vlan.c\n+++ b/net/bridge/br_vlan.c\n@@ -35,21 +35,21 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)\n }\n \n static void __vlan_add_pvid(struct net_bridge_vlan_group *vg,\n-\t\t\t    const struct net_bridge_vlan *v)\n+\t\t\t    struct net_bridge_vlan *v)\n {\n-\tif (vg-\u003epvid == v-\u003evid)\n+\tif (rcu_access_pointer(vg-\u003epvid) == v)\n \t\treturn;\n \n-\tbr_vlan_set_pvid_state(vg, br_vlan_get_state(v));\n-\tWRITE_ONCE(vg-\u003epvid, v-\u003evid);\n+\tRCU_INIT_POINTER(vg-\u003epvid, v);\n }\n \n-static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)\n+static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg,\n+\t\t\t       struct net_bridge_vlan *v)\n {\n-\tif (vg-\u003epvid != vid)\n+\tif (rcu_access_pointer(vg-\u003epvid) != v)\n \t\treturn;\n \n-\tWRITE_ONCE(vg-\u003epvid, 0);\n+\tRCU_INIT_POINTER(vg-\u003epvid, NULL);\n }\n \n /* Update the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED flags of @v.\n@@ -60,6 +60,7 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,\n \t\t\t\tbool commit)\n {\n \tstruct net_bridge_vlan_group *vg;\n+\tstruct net_bridge_vlan *pvid;\n \tu16 vlan_flags;\n \tbool change;\n \n@@ -70,7 +71,8 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,\n \n \t/* check if anything would be changed on commit */\n \tvlan_flags = v-\u003eflags;\n-\tchange = !!(flags \u0026 BRIDGE_VLAN_INFO_PVID) == !!(vg-\u003epvid != v-\u003evid) ||\n+\tpvid = rtnl_dereference(vg-\u003epvid);\n+\tchange = !!(flags \u0026 BRIDGE_VLAN_INFO_PVID) == !!(pvid != v) ||\n \t\t ((flags ^ vlan_flags) \u0026 BRIDGE_VLAN_INFO_UNTAGGED);\n \n \tif (!commit)\n@@ -79,7 +81,7 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,\n \tif (flags \u0026 BRIDGE_VLAN_INFO_PVID)\n \t\t__vlan_add_pvid(vg, v);\n \telse\n-\t\t__vlan_delete_pvid(vg, v-\u003evid);\n+\t\t__vlan_delete_pvid(vg, v);\n \n \tif (flags \u0026 BRIDGE_VLAN_INFO_UNTAGGED)\n \t\tvlan_flags |= BRIDGE_VLAN_INFO_UNTAGGED;\n@@ -128,10 +130,12 @@ static void __vlan_add_list(struct net_bridge_vlan *v)\n \tstruct list_head *headp, *hpos;\n \tstruct net_bridge_vlan *vent;\n \n-\tif (br_vlan_is_master(v))\n+\tif (br_vlan_is_master(v)) {\n \t\tvg = br_vlan_group(v-\u003ebr);\n-\telse\n+\t} else {\n \t\tvg = nbp_vlan_group(v-\u003eport);\n+\t\tlist_add_rcu(\u0026v-\u003eport_vlist, \u0026v-\u003ebrvlan-\u003eport_vlist);\n+\t}\n \n \theadp = \u0026vg-\u003evlan_list;\n \tlist_for_each_prev(hpos, headp) {\n@@ -145,6 +149,10 @@ static void __vlan_add_list(struct net_bridge_vlan *v)\n static void __vlan_del_list(struct net_bridge_vlan *v)\n {\n \tlist_del_rcu(\u0026v-\u003evlist);\n+\tif (br_vlan_is_master(v))\n+\t\tWARN_ON(!list_empty(\u0026v-\u003eport_vlist));\n+\telse\n+\t\tlist_del_rcu(\u0026v-\u003eport_vlist);\n }\n \n static int __vlan_vid_del(struct net_device *dev, struct net_bridge *br,\n@@ -250,6 +258,36 @@ static void br_vlan_init_state(struct net_bridge_vlan *v)\n \tv-\u003emsti = 0;\n }\n \n+static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)\n+{\n+\treturn refcount_read(\u0026masterv-\u003erefcnt) - br_vlan_is_brentry(masterv);\n+}\n+\n+static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,\n+\t\t\t\t       unsigned int count)\n+{\n+\tstruct net_bridge_vlan_port_array *array = NULL, *old;\n+\tunsigned int i = 0;\n+\n+\tWARN_ON(!br_vlan_is_master(masterv));\n+\n+\tif (count \u003e BR_VLAN_PORT_ARRAY_THRESHOLD)\n+\t\tarray = kvmalloc(struct_size(array, vlans, count), GFP_KERNEL);\n+\n+\tif (array) {\n+\t\tstruct net_bridge_vlan *pv;\n+\n+\t\tarray-\u003ecount = count;\n+\t\tlist_for_each_entry(pv, \u0026masterv-\u003eport_vlist, port_vlist)\n+\t\t\tarray-\u003evlans[i++] = pv;\n+\t}\n+\n+\told = rtnl_dereference(masterv-\u003eport_array);\n+\trcu_assign_pointer(masterv-\u003eport_array, array);\n+\tif (old)\n+\t\tkvfree_rcu(old, rcu);\n+}\n+\n /* This is the shared VLAN add function which works for both ports and bridge\n  * devices. There are four possible calls to this function in terms of the\n  * vlan entry type:\n@@ -333,6 +371,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n \t\t}\n \t\tbr_multicast_ctx_init(br, v, \u0026v-\u003ebr_mcast_ctx);\n \t\tv-\u003epriv_flags |= BR_VLFLAG_GLOBAL_MCAST_ENABLED;\n+\t\tINIT_LIST_HEAD(\u0026v-\u003eport_vlist);\n \t}\n \n \t/* Add the dev mac and count the vlan only if it's usable */\n@@ -359,8 +398,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n \t__vlan_flags_commit(v, flags);\n \tbr_multicast_toggle_one_vlan(v, true);\n \n-\tif (p)\n+\tif (p) {\n+\t\tbr_vlan_rebuild_port_array(masterv, br_vlan_num_ports(masterv));\n \t\tnbp_vlan_set_vlan_dev_state(p, v-\u003evid);\n+\t}\n out:\n \treturn err;\n \n@@ -403,7 +444,7 @@ static void __vlan_del(struct net_bridge_vlan *v)\n \t\tmasterv = v-\u003ebrvlan;\n \t}\n \n-\t__vlan_delete_pvid(vg, v-\u003evid);\n+\t__vlan_delete_pvid(vg, v);\n \tif (p) {\n \t\terr = __vlan_vid_del(p-\u003edev, p-\u003ebr, v);\n \t\tif (err)\n@@ -429,6 +470,9 @@ static void __vlan_del(struct net_bridge_vlan *v)\n \t\trhashtable_remove_fast(\u0026vg-\u003evlan_hash, \u0026v-\u003evnode,\n \t\t\t\t       br_vlan_rht_params);\n \t\t__vlan_del_list(v);\n+\t\t/* -1 because br_vlan_put_master() is called later */\n+\t\tbr_vlan_rebuild_port_array(masterv,\n+\t\t\t\t\t   br_vlan_num_ports(masterv) - 1);\n \t\tnbp_vlan_set_vlan_dev_state(p, v-\u003evid);\n \t\tbr_multicast_toggle_one_vlan(v, false);\n \t\tbr_multicast_port_ctx_deinit(\u0026v-\u003eport_mcast_ctx);\n@@ -453,7 +497,7 @@ static void __vlan_flush(const struct net_bridge *br,\n \tstruct net_bridge_vlan *vlan, *tmp;\n \tu16 v_start = 0, v_end = 0;\n \n-\t__vlan_delete_pvid(vg, vg-\u003epvid);\n+\t__vlan_delete_pvid(vg, rtnl_dereference(vg-\u003epvid));\n \tlist_for_each_entry_safe(vlan, tmp, \u0026vg-\u003evlan_list, vlist) {\n \t\t/* take care of disjoint ranges */\n \t\tif (!v_start) {\n@@ -476,10 +520,10 @@ static void __vlan_flush(const struct net_bridge *br,\n struct sk_buff *br_handle_vlan(struct net_bridge *br,\n \t\t\t       const struct net_bridge_port *p,\n \t\t\t       struct net_bridge_vlan_group *vg,\n+\t\t\t       struct net_bridge_vlan *v,\n \t\t\t       struct sk_buff *skb)\n {\n \tstruct pcpu_sw_netstats *stats;\n-\tstruct net_bridge_vlan *v;\n \tu16 vid;\n \n \t/* If this packet was not filtered at input, let it pass */\n@@ -490,19 +534,22 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,\n \t * a valid vlan id.  If the vlan id has untagged flag set,\n \t * send untagged; otherwise, send tagged.\n \t */\n-\tbr_vlan_get_tag(skb, \u0026vid);\n-\tv = br_vlan_find(vg, vid);\n-\t/* Vlan entry must be configured at this point.  The\n-\t * only exception is the bridge is set in promisc mode and the\n-\t * packet is destined for the bridge device.  In this case\n-\t * pass the packet as is.\n-\t */\n-\tif (!v || !br_vlan_should_use(v)) {\n-\t\tif ((br-\u003edev-\u003eflags \u0026 IFF_PROMISC) \u0026\u0026 skb-\u003edev == br-\u003edev) {\n-\t\t\tgoto out;\n-\t\t} else {\n-\t\t\tkfree_skb(skb);\n-\t\t\treturn NULL;\n+\tif (!v) {\n+\t\tbr_vlan_get_tag(skb, \u0026vid);\n+\t\tv = br_vlan_find(vg, vid);\n+\t\t/* Vlan entry must be configured at this point.  The\n+\t\t * only exception is the bridge is set in promisc mode and the\n+\t\t * packet is destined for the bridge device.  In this case\n+\t\t * pass the packet as is.\n+\t\t */\n+\t\tif (!v || !br_vlan_should_use(v)) {\n+\t\t\tif ((br-\u003edev-\u003eflags \u0026 IFF_PROMISC) \u0026\u0026\n+\t\t\t    skb-\u003edev == br-\u003edev) {\n+\t\t\t\tgoto out;\n+\t\t\t} else {\n+\t\t\t\tkfree_skb(skb);\n+\t\t\t\treturn NULL;\n+\t\t\t}\n \t\t}\n \t}\n \tif (br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {\n@@ -579,42 +626,32 @@ static bool __allowed_ingress(const struct net_bridge *br,\n \t}\n \n \tif (!*vid) {\n-\t\tu16 pvid = br_get_pvid(vg);\n-\n+\t\tv = vg ? rcu_dereference(vg-\u003epvid) : NULL;\n \t\t/* Frame had a tag with VID 0 or did not have a tag.\n \t\t * See if pvid is set on this port.  That tells us which\n \t\t * vlan untagged or priority-tagged traffic belongs to.\n \t\t */\n-\t\tif (!pvid)\n+\t\tif (!v)\n \t\t\tgoto drop;\n \n \t\t/* PVID is set on this port.  Any untagged or priority-tagged\n \t\t * ingress frame is considered to belong to this vlan.\n \t\t */\n-\t\t*vid = pvid;\n+\t\t*vid = v-\u003evid;\n \t\tif (likely(!tagged))\n \t\t\t/* Untagged Frame. */\n-\t\t\t__vlan_hwaccel_put_tag(skb, br-\u003evlan_proto, pvid);\n+\t\t\t__vlan_hwaccel_put_tag(skb, br-\u003evlan_proto, v-\u003evid);\n \t\telse\n \t\t\t/* Priority-tagged Frame.\n \t\t\t * At this point, we know that skb-\u003evlan_tci VID\n \t\t\t * field was 0.\n \t\t\t * We update only VID field and preserve PCP field.\n \t\t\t */\n-\t\t\tskb-\u003evlan_tci |= pvid;\n-\n-\t\t/* if snooping and stats are disabled we can avoid the lookup */\n-\t\tif (!br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) \u0026\u0026\n-\t\t    !br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {\n-\t\t\tif (*state == BR_STATE_FORWARDING) {\n-\t\t\t\t*state = br_vlan_get_pvid_state(vg);\n-\t\t\t\tif (!br_vlan_state_allowed(*state, true))\n-\t\t\t\t\tgoto drop;\n-\t\t\t}\n-\t\t\treturn true;\n-\t\t}\n+\t\t\tskb-\u003evlan_tci |= v-\u003evid;\n+\t} else {\n+\t\tv = br_vlan_find(vg, *vid);\n \t}\n-\tv = br_vlan_find(vg, *vid);\n+\n \tif (!v || !br_vlan_should_use(v))\n \t\tgoto drop;\n \n@@ -697,11 +734,10 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)\n \t\t*vid = 0;\n \n \tif (!*vid) {\n-\t\t*vid = br_get_pvid(vg);\n-\t\tif (!*vid ||\n-\t\t    !br_vlan_state_allowed(br_vlan_get_pvid_state(vg), true))\n+\t\tv = rcu_dereference(vg-\u003epvid);\n+\t\tif (!v || !br_vlan_state_allowed(br_vlan_get_state(v), true))\n \t\t\treturn false;\n-\n+\t\t*vid = v-\u003evid;\n \t\treturn true;\n \t}\n \n@@ -1061,17 +1097,10 @@ int br_vlan_set_stats_per_port(struct net_bridge *br, unsigned long val)\n \n static bool vlan_default_pvid(struct net_bridge_vlan_group *vg, u16 vid)\n {\n-\tstruct net_bridge_vlan *v;\n+\tstruct net_bridge_vlan *pvid = rtnl_dereference(vg-\u003epvid);\n \n-\tif (vid != vg-\u003epvid)\n-\t\treturn false;\n-\n-\tv = br_vlan_lookup(\u0026vg-\u003evlan_hash, vid);\n-\tif (v \u0026\u0026 br_vlan_should_use(v) \u0026\u0026\n-\t    (v-\u003eflags \u0026 BRIDGE_VLAN_INFO_UNTAGGED))\n-\t\treturn true;\n-\n-\treturn false;\n+\treturn pvid \u0026\u0026 pvid-\u003evid == vid \u0026\u0026 br_vlan_should_use(pvid) \u0026\u0026\n+\t       (pvid-\u003eflags \u0026 BRIDGE_VLAN_INFO_UNTAGGED);\n }\n \n static void br_vlan_disable_default_pvid(struct net_bridge *br)\n@@ -1524,7 +1553,7 @@ int br_vlan_get_info(const struct net_device *dev, u16 vid,\n \n \tp_vinfo-\u003evid = vid;\n \tp_vinfo-\u003eflags = v-\u003eflags;\n-\tif (vid == br_get_pvid(vg))\n+\tif (v == rcu_access_pointer(vg-\u003epvid))\n \t\tp_vinfo-\u003eflags |= BRIDGE_VLAN_INFO_PVID;\n \treturn 0;\n }\n@@ -1551,7 +1580,7 @@ int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,\n \n \tp_vinfo-\u003evid = vid;\n \tp_vinfo-\u003eflags = READ_ONCE(v-\u003eflags);\n-\tif (vid == br_get_pvid(vg))\n+\tif (v == rcu_access_pointer(vg-\u003epvid))\n \t\tp_vinfo-\u003eflags |= BRIDGE_VLAN_INFO_PVID;\n \treturn 0;\n }\n@@ -1956,7 +1985,7 @@ void br_vlan_notify(const struct net_bridge *br,\n \t\t\tgoto out_kfree;\n \n \t\tflags = v-\u003eflags;\n-\t\tif (br_get_pvid(vg) == v-\u003evid)\n+\t\tif (v == rcu_access_pointer(vg-\u003epvid))\n \t\t\tflags |= BRIDGE_VLAN_INFO_PVID;\n \t\tbreak;\n \tcase RTM_DELVLAN:\ndiff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c\nindex 506668fc31d9d..e4b7a147e42b2 100644\n--- a/net/bridge/br_vlan_options.c\n+++ b/net/bridge/br_vlan_options.c\n@@ -110,8 +110,7 @@ size_t br_vlan_opts_nl_size(void)\n \t       + 0;\n }\n \n-static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,\n-\t\t\t\tstruct net_bridge_vlan *v,\n+static int br_vlan_modify_state(struct net_bridge_vlan *v,\n \t\t\t\tu8 state,\n \t\t\t\tbool *changed,\n \t\t\t\tstruct netlink_ext_ack *extack)\n@@ -143,9 +142,6 @@ static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,\n \tif (v-\u003estate == state)\n \t\treturn 0;\n \n-\tif (v-\u003evid == br_get_pvid(vg))\n-\t\tbr_vlan_set_pvid_state(vg, state);\n-\n \tbr_vlan_set_state(v, state);\n \t*changed = true;\n \n@@ -216,7 +212,6 @@ static int br_vlan_modify_tunnel(const struct net_bridge_port *p,\n \n static int br_vlan_process_one_opts(const struct net_bridge *br,\n \t\t\t\t    const struct net_bridge_port *p,\n-\t\t\t\t    struct net_bridge_vlan_group *vg,\n \t\t\t\t    struct net_bridge_vlan *v,\n \t\t\t\t    struct nlattr **tb,\n \t\t\t\t    bool *changed,\n@@ -228,7 +223,7 @@ static int br_vlan_process_one_opts(const struct net_bridge *br,\n \tif (tb[BRIDGE_VLANDB_ENTRY_STATE]) {\n \t\tu8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);\n \n-\t\terr = br_vlan_modify_state(vg, v, state, changed, extack);\n+\t\terr = br_vlan_modify_state(v, state, changed, extack);\n \t\tif (err)\n \t\t\treturn err;\n \t}\n@@ -339,8 +334,7 @@ int br_vlan_process_options(const struct net_bridge *br,\n \t\t\tbreak;\n \t\t}\n \n-\t\terr = br_vlan_process_one_opts(br, p, vg, v, tb, \u0026changed,\n-\t\t\t\t\t       extack);\n+\t\terr = br_vlan_process_one_opts(br, p, v, tb, \u0026changed, extack);\n \t\tif (err)\n \t\t\tbreak;\n \n"
}

2/1 2026/09/20 21:30 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "BRIDGE",
    "BRIDGE_VLAN_FILTERING",
    "VLAN_8021Q",
    "BRIDGE_IGMP_SNOOPING",
    "INET"
  ],
  "FocusSymbols": [
    "br_flood",
    "br_flood_vlan",
    "br_vlan_rebuild_port_array",
    "br_handle_vlan",
    "br_is_neigh_suppress_enabled",
    "br_is_neigh_forward_grat_enabled",
    "br_mst_set_state",
    "nbp_vlan_add",
    "nbp_vlan_delete",
    "br_multicast_flood"
  ],
  "Reasoning": "The patch modifies the Linux Ethernet Bridge subsystem by introducing an RCU-protected port array and list on master VLAN entries to optimize VLAN-aware packet flooding (br_flood, br_flood_vlan). It restructures bridge forwarding paths, changes how PVID and neighbor suppression flags are queried, introduces dynamic memory allocation (kvmalloc/kvfree_rcu), and adds WARN_ON assertions. Because this modifies reachable core networking code accessible via standard virtual network interfaces and netlink commands, fuzzing is warranted to detect potential memory corruption, RCU use-after-free, or invariant violations.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit d167430449dc7374d66f5cff67b852f548e6a02b
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Sep 20 21:30:13 2026 +0000

    syz-cluster: applied patch under review

diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index 9ce8f440e38fa..da15f4d7c1aef 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -40,6 +40,23 @@ void br_recalculate_neigh_suppress_enabled(struct net_bridge *br)
 }
 
 #if IS_ENABLED(CONFIG_INET)
+static bool
+br_is_neigh_suppress_enabled_vid(const struct net_bridge_port *p, u16 vid)
+{
+	const struct net_bridge_vlan *v = NULL;
+
+	if (p && vid && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags)) {
+		struct net_bridge_vlan_group *vg;
+
+		vg = nbp_vlan_group_rcu(p);
+		v = br_vlan_find(vg, vid);
+		if (!v)
+			return false;
+	}
+
+	return br_is_neigh_suppress_enabled(p, v);
+}
+
 static void br_arp_send(struct net_bridge *br, struct net_bridge_port *p,
 			struct net_device *dev, __be32 dest_ip, __be32 src_ip,
 			const unsigned char *dest_hw,
@@ -159,7 +176,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 		return;
 
 	if (br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) {
-		if (br_is_neigh_suppress_enabled(p, vid))
+		if (br_is_neigh_suppress_enabled_vid(p, vid))
 			return;
 		if (is_unicast_ether_addr(eth_hdr(skb)->h_dest) &&
 		    parp->ar_op == htons(ARPOP_REQUEST))
@@ -211,7 +228,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 
 			if ((p && test_bit(BR_PROXYARP_BIT, &p->flags)) ||
 			    (dst && test_bit(BR_PROXYARP_WIFI_BIT, &dst->flags)) ||
-			    br_is_neigh_suppress_enabled(dst, vid)) {
+			    br_is_neigh_suppress_enabled_vid(dst, vid)) {
 				if (!vid)
 					br_arp_send(br, p, skb->dev, sip, tip,
 						    sha, ha, sha, 0, 0);
@@ -424,7 +441,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 	BR_INPUT_SKB_CB(skb)->proxyarp_replied = 0;
 	BR_INPUT_SKB_CB(skb)->grat_arp = 0;
 
-	if (br_is_neigh_suppress_enabled(p, vid))
+	if (br_is_neigh_suppress_enabled_vid(p, vid))
 		return;
 
 	if (is_unicast_ether_addr(eth_hdr(skb)->h_dest) &&
@@ -486,7 +503,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 			const struct net_bridge_port *dst = READ_ONCE(f->dst);
 			bool replied = false;
 
-			if (br_is_neigh_suppress_enabled(dst, vid)) {
+			if (br_is_neigh_suppress_enabled_vid(dst, vid)) {
 				if (vid != 0)
 					br_nd_send(br, p, skb, n, ha,
 						   skb->vlan_proto,
@@ -509,35 +526,25 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 }
 #endif
 
-bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid)
+bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p,
+				  const struct net_bridge_vlan *v)
 {
 	if (!p)
 		return false;
 
-	if (vid && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags)) {
-		struct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p);
-		struct net_bridge_vlan *v;
-
-		v = br_vlan_find(vg, vid);
-		if (!v)
-			return false;
+	if (v && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags))
 		return !!(READ_ONCE(v->priv_flags) &
 			  BR_VLFLAG_NEIGH_SUPPRESS_ENABLED);
-	}
+
 	return test_bit(BR_NEIGH_SUPPRESS_BIT, &p->flags);
 }
 
-bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid)
+bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p,
+				      const struct net_bridge_vlan *v)
 {
-	if (vid && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags)) {
-		struct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p);
-		struct net_bridge_vlan *v;
-
-		v = br_vlan_find(vg, vid);
-		if (!v)
-			return false;
+	if (v && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags))
 		return !!(READ_ONCE(v->priv_flags) &
 			  BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED);
-	}
+
 	return test_bit(BR_NEIGH_FORWARD_GRAT_BIT, &p->flags);
 }
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index e01c44a90d84b..ce9ea9ac3d0ad 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -89,10 +89,10 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 
 	dest = eth_hdr(skb)->h_dest;
 	if (is_broadcast_ether_addr(dest)) {
-		br_flood(br, skb, BR_PKT_BROADCAST, false, true, vid);
+		br_flood(br, vlan, skb, BR_PKT_BROADCAST, false, true);
 	} else if (is_multicast_ether_addr(dest)) {
 		if (unlikely(netpoll_tx_running(dev))) {
-			br_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);
+			br_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);
 			goto out;
 		}
 		if (br_multicast_rcv(&brmctx, &pmctx_null, vlan, skb, vid)) {
@@ -105,11 +105,11 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 		    br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst))
 			br_multicast_flood(mdst, skb, brmctx, false, true);
 		else
-			br_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);
+			br_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);
 	} else if ((dst = br_fdb_find_rcu(br, dest, vid)) != NULL) {
 		br_forward(READ_ONCE(dst->dst), skb, false, true);
 	} else {
-		br_flood(br, skb, BR_PKT_UNICAST, false, true, vid);
+		br_flood(br, vlan, skb, BR_PKT_UNICAST, false, true);
 	}
 out:
 	rcu_read_unlock();
diff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c
index 46c762ca5177e..b5eece1ff9e8c 100644
--- a/net/bridge/br_forward.c
+++ b/net/bridge/br_forward.c
@@ -7,7 +7,6 @@
  *	Lennert Buytenhek		<buytenh@gnu.org>
  */
 
-#include <linux/err.h>
 #include <linux/slab.h>
 #include <linux/kernel.h>
 #include <linux/netdevice.h>
@@ -17,16 +16,31 @@
 #include <linux/netfilter_bridge.h>
 #include "br_private.h"
 
+struct br_fwd_dst {
+	const struct net_bridge_port *port;
+	struct net_bridge_vlan *vlan;
+};
+
+static bool should_deliver_vlan(const struct br_fwd_dst *fwd,
+				const struct sk_buff *skb)
+{
+	if (fwd->vlan)
+		return br_vlan_state_allowed(br_vlan_get_state(fwd->vlan),
+					     false);
+
+	return br_allowed_egress(nbp_vlan_group_rcu(fwd->port), skb);
+}
+
 /* Don't forward packets to originating port or forwarding disabled */
-static inline int should_deliver(const struct net_bridge_port *p,
-				 const struct sk_buff *skb)
+static __always_inline bool should_deliver(const struct br_fwd_dst *fwd,
+					   const struct sk_buff *skb)
 {
-	struct net_bridge_vlan_group *vg;
+	const struct net_bridge_port *p = fwd->port;
 
-	vg = nbp_vlan_group_rcu(p);
 	return (test_bit(BR_HAIRPIN_MODE_BIT, &p->flags) || skb->dev != p->dev) &&
 		(br_mst_is_enabled(p) || p->state == BR_STATE_FORWARDING) &&
-		br_allowed_egress(vg, skb) && nbp_switchdev_allowed_egress(p, skb) &&
+		should_deliver_vlan(fwd, skb) &&
+		nbp_switchdev_allowed_egress(p, skb) &&
 		!br_skb_isolated(p, skb);
 }
 
@@ -70,9 +84,10 @@ int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
 }
 EXPORT_SYMBOL_GPL(br_forward_finish);
 
-static void __br_forward(const struct net_bridge_port *to,
+static void __br_forward(const struct br_fwd_dst *fwd,
 			 struct sk_buff *skb, bool local_orig)
 {
+	const struct net_bridge_port *to = fwd->port;
 	struct net_bridge_vlan_group *vg;
 	struct net_device *indev;
 	struct net *net;
@@ -83,8 +98,8 @@ static void __br_forward(const struct net_bridge_port *to,
 	 */
 	nbp_switchdev_frame_mark_tx_fwd_offload(to, skb);
 
-	vg = nbp_vlan_group_rcu(to);
-	skb = br_handle_vlan(to->br, to, vg, skb);
+	vg = fwd->vlan ? NULL : nbp_vlan_group_rcu(to);
+	skb = br_handle_vlan(to->br, to, vg, fwd->vlan, skb);
 	if (!skb)
 		return;
 
@@ -117,7 +132,7 @@ static void __br_forward(const struct net_bridge_port *to,
 		br_forward_finish);
 }
 
-static int deliver_clone(const struct net_bridge_port *prev,
+static int deliver_clone(const struct br_fwd_dst *fwd,
 			 struct sk_buff *skb, bool local_orig)
 {
 	struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
@@ -128,7 +143,7 @@ static int deliver_clone(const struct net_bridge_port *prev,
 		return -ENOMEM;
 	}
 
-	__br_forward(prev, skb, local_orig);
+	__br_forward(fwd, skb, local_orig);
 	return 0;
 }
 
@@ -144,6 +159,8 @@ static int deliver_clone(const struct net_bridge_port *prev,
 void br_forward(const struct net_bridge_port *to,
 		struct sk_buff *skb, bool local_rcv, bool local_orig)
 {
+	struct br_fwd_dst fwd;
+
 	if (unlikely(!to))
 		goto out;
 
@@ -159,11 +176,13 @@ void br_forward(const struct net_bridge_port *to,
 		to = backup_port;
 	}
 
-	if (should_deliver(to, skb)) {
+	fwd.port = to;
+	fwd.vlan = NULL;
+	if (should_deliver(&fwd, skb)) {
 		if (local_rcv)
-			deliver_clone(to, skb, local_orig);
+			deliver_clone(&fwd, skb, local_orig);
 		else
-			__br_forward(to, skb, local_orig);
+			__br_forward(&fwd, skb, local_orig);
 		return;
 	}
 
@@ -173,95 +192,162 @@ void br_forward(const struct net_bridge_port *to,
 }
 EXPORT_SYMBOL_GPL(br_forward);
 
-static struct net_bridge_port *maybe_deliver(
-	struct net_bridge_port *prev, struct net_bridge_port *p,
-	struct sk_buff *skb, bool local_orig)
+static int maybe_deliver(struct br_fwd_dst *prev, const struct br_fwd_dst *fwd,
+			 struct sk_buff *skb, bool local_orig)
 {
+	const struct net_bridge_port *p = fwd->port;
 	u8 igmp_type = br_multicast_igmp_type(skb);
 	int err;
 
-	if (!should_deliver(p, skb))
-		return prev;
+	if (!should_deliver(fwd, skb))
+		return 0;
 
 	nbp_switchdev_frame_mark_tx_fwd_to_hwdom(p, skb);
 
-	if (!prev)
+	if (!prev->port)
 		goto out;
 
 	err = deliver_clone(prev, skb, local_orig);
 	if (err)
-		return ERR_PTR(err);
+		return err;
 out:
 	br_multicast_count(p->br, p, skb, igmp_type, BR_MCAST_DIR_TX);
+	*prev = *fwd;
 
-	return p;
+	return 0;
 }
 
-/* called under rcu_read_lock */
-void br_flood(struct net_bridge *br, struct sk_buff *skb,
-	      enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,
-	      u16 vid)
+static void br_flood_finish(const struct br_fwd_dst *fwd, int err,
+			    struct sk_buff *skb,
+			    bool local_rcv, bool local_orig)
 {
 	enum skb_drop_reason reason = SKB_DROP_REASON_NO_TX_TARGET;
-	struct net_bridge_port *prev = NULL;
-	struct net_bridge_port *p;
 
-	br_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);
+	if (err || !fwd->port) {
+		if (err)
+			reason = err == -ENOMEM ? SKB_DROP_REASON_NOMEM :
+						  SKB_DROP_REASON_NOT_SPECIFIED;
+
+		if (!local_rcv)
+			kfree_skb_reason(skb, reason);
+		return;
+	}
 
-	list_for_each_entry_rcu(p, &br->port_list, list) {
-		/* Do not flood unicast traffic to ports that turn it off, nor
-		 * other traffic if flood off, except for traffic we originate
+	if (local_rcv)
+		deliver_clone(fwd, skb, local_orig);
+	else
+		__br_forward(fwd, skb, local_orig);
+}
+
+static int br_flood_port(struct br_fwd_dst *prev,
+			 const struct br_fwd_dst *fwd, struct sk_buff *skb,
+			 enum br_pkt_type pkt_type, bool local_orig)
+{
+	const struct net_bridge_port *p = fwd->port;
+
+	/* Do not flood unicast traffic to ports that turn it off, nor
+	 * other traffic if flood off, except for traffic we originate
+	 */
+	switch (pkt_type) {
+	case BR_PKT_UNICAST:
+		if (!test_bit(BR_FLOOD_BIT, &p->flags))
+			return 0;
+		break;
+	case BR_PKT_MULTICAST:
+		if (!test_bit(BR_MCAST_FLOOD_BIT, &p->flags) &&
+		    skb->dev != p->br->dev)
+			return 0;
+		break;
+	case BR_PKT_BROADCAST:
+		if (!test_bit(BR_BCAST_FLOOD_BIT, &p->flags) &&
+		    skb->dev != p->br->dev)
+			return 0;
+		break;
+	}
+
+	/* Do not flood to ports that enable proxy ARP */
+	if (test_bit(BR_PROXYARP_BIT, &p->flags))
+		return 0;
+	if (BR_INPUT_SKB_CB(skb)->proxyarp_replied) {
+		if (test_bit(BR_PROXYARP_WIFI_BIT, &p->flags))
+			return 0;
+		/* For gratuitous ARPs/NAs, check neigh_forward_grat.
+		 * For regular ARPs/NDs, check only neigh_suppress.
 		 */
-		switch (pkt_type) {
-		case BR_PKT_UNICAST:
-			if (!test_bit(BR_FLOOD_BIT, &p->flags))
-				continue;
-			break;
-		case BR_PKT_MULTICAST:
-			if (!test_bit(BR_MCAST_FLOOD_BIT, &p->flags) && skb->dev != br->dev)
-				continue;
-			break;
-		case BR_PKT_BROADCAST:
-			if (!test_bit(BR_BCAST_FLOOD_BIT, &p->flags) && skb->dev != br->dev)
-				continue;
-			break;
-		}
+		if (br_is_neigh_suppress_enabled(p, fwd->vlan) &&
+		    (!BR_INPUT_SKB_CB(skb)->grat_arp ||
+		     !br_is_neigh_forward_grat_enabled(p, fwd->vlan)))
+			return 0;
+	}
 
-		/* Do not flood to ports that enable proxy ARP */
-		if (test_bit(BR_PROXYARP_BIT, &p->flags))
-			continue;
-		if (BR_INPUT_SKB_CB(skb)->proxyarp_replied) {
-			if (test_bit(BR_PROXYARP_WIFI_BIT, &p->flags))
-				continue;
-			/* For gratuitous ARPs/NAs, check neigh_forward_grat.
-			 * For regular ARPs/NDs, check only neigh_suppress.
-			 */
-			if (br_is_neigh_suppress_enabled(p, vid) &&
-			    (!BR_INPUT_SKB_CB(skb)->grat_arp ||
-			     !br_is_neigh_forward_grat_enabled(p, vid)))
-				continue;
-		}
+	return maybe_deliver(prev, fwd, skb, local_orig);
+}
 
-		prev = maybe_deliver(prev, p, skb, local_orig);
-		if (IS_ERR(prev)) {
-			reason = PTR_ERR(prev) == -ENOMEM ? SKB_DROP_REASON_NOMEM :
-				 SKB_DROP_REASON_NOT_SPECIFIED;
-			goto out;
+static int br_flood_vlan(struct br_fwd_dst *prev,
+			 struct net_bridge_vlan *v, struct sk_buff *skb,
+			 enum br_pkt_type pkt_type, bool local_orig)
+{
+	struct net_bridge_vlan_port_array *array;
+	struct net_bridge_vlan *masterv, *pv;
+	struct br_fwd_dst dst;
+	int err;
+
+	masterv = br_vlan_is_master(v) ? v : v->brvlan;
+	array = rcu_dereference(masterv->port_array);
+	if (array) {
+		unsigned int i;
+
+		for (i = 0; i < array->count; i++) {
+			pv = array->vlans[i];
+			dst.port = pv->port;
+			dst.vlan = pv;
+			err = br_flood_port(prev, &dst, skb, pkt_type,
+					    local_orig);
+			if (err)
+				return err;
+		}
+	} else {
+		list_for_each_entry_rcu(pv, &masterv->port_vlist, port_vlist) {
+			dst.port = pv->port;
+			dst.vlan = pv;
+			err = br_flood_port(prev, &dst, skb, pkt_type,
+					    local_orig);
+			if (err)
+				return err;
 		}
 	}
 
-	if (!prev)
-		goto out;
+	return 0;
+}
 
-	if (local_rcv)
-		deliver_clone(prev, skb, local_orig);
-	else
-		__br_forward(prev, skb, local_orig);
-	return;
+/* called under rcu_read_lock */
+void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,
+	      struct sk_buff *skb, enum br_pkt_type pkt_type,
+	      bool local_rcv, bool local_orig)
+{
+	struct br_fwd_dst prev = {};
+	int err = 0;
 
-out:
-	if (!local_rcv)
-		kfree_skb_reason(skb, reason);
+	br_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);
+
+	if (v) {
+		err = br_flood_vlan(&prev, v, skb, pkt_type, local_orig);
+	} else {
+		struct net_bridge_port *p;
+
+		list_for_each_entry_rcu(p, &br->port_list, list) {
+			struct br_fwd_dst fwd = {
+				.port = p,
+			};
+
+			err = br_flood_port(&prev, &fwd, skb, pkt_type,
+					    local_orig);
+			if (err)
+				break;
+		}
+	}
+
+	br_flood_finish(&prev, err, skb, local_rcv, local_orig);
 }
 
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
@@ -270,9 +356,12 @@ static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,
 {
 	struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
 	const unsigned char *src = eth_hdr(skb)->h_source;
+	struct br_fwd_dst fwd = {
+		.port = p,
+	};
 	struct sk_buff *nskb;
 
-	if (!should_deliver(p, skb))
+	if (!should_deliver(&fwd, skb))
 		return;
 
 	/* Even with hairpin, no soliloquies - prevent breaking IPv6 DAD */
@@ -292,7 +381,7 @@ static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,
 	if (!is_broadcast_ether_addr(addr))
 		memcpy(eth_hdr(skb)->h_dest, addr, ETH_ALEN);
 
-	__br_forward(p, skb, local_orig);
+	__br_forward(&fwd, skb, local_orig);
 }
 
 /* called with rcu_read_lock */
@@ -301,11 +390,11 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 			struct net_bridge_mcast *brmctx,
 			bool local_rcv, bool local_orig)
 {
-	enum skb_drop_reason reason = SKB_DROP_REASON_NO_TX_TARGET;
-	struct net_bridge_port *prev = NULL;
 	struct net_bridge_port_group *p;
 	bool allow_mode_include = true;
+	struct br_fwd_dst prev = {};
 	struct hlist_node *rp;
+	int err = 0;
 
 	rp = br_multicast_get_first_rport_node(brmctx, skb);
 
@@ -321,6 +410,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 
 	while (p || rp) {
 		struct net_bridge_port *port, *lport, *rport;
+		struct br_fwd_dst fwd = {};
 
 		lport = p ? p->key.port : NULL;
 		rport = br_multicast_rport_from_node_skb(rp, skb);
@@ -342,12 +432,11 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 			port = rport;
 		}
 
-		prev = maybe_deliver(prev, port, skb, local_orig);
-		if (IS_ERR(prev)) {
-			reason = PTR_ERR(prev) == -ENOMEM ? SKB_DROP_REASON_NOMEM :
-				 SKB_DROP_REASON_NOT_SPECIFIED;
-			goto out;
-		}
+		fwd.port = port;
+		err = maybe_deliver(&prev, &fwd, skb, local_orig);
+		if (err)
+			break;
+
 delivered:
 		if ((unsigned long)lport >= (unsigned long)port)
 			p = rcu_dereference(p->next);
@@ -355,17 +444,6 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 			rp = rcu_dereference(hlist_next_rcu(rp));
 	}
 
-	if (!prev)
-		goto out;
-
-	if (local_rcv)
-		deliver_clone(prev, skb, local_orig);
-	else
-		__br_forward(prev, skb, local_orig);
-	return;
-
-out:
-	if (!local_rcv)
-		kfree_skb_reason(skb, reason);
+	br_flood_finish(&prev, err, skb, local_rcv, local_orig);
 }
 #endif
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 8bed72baf1615..4357d78524a68 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -58,7 +58,7 @@ static int br_pass_frame_up(struct sk_buff *skb, bool promisc)
 
 	indev = skb->dev;
 	skb->dev = brdev;
-	skb = br_handle_vlan(br, NULL, vg, skb);
+	skb = br_handle_vlan(br, NULL, vg, NULL, skb);
 	if (!skb)
 		return NET_RX_DROP;
 	/* update the multicast stats if the packet is IGMP/MLD */
@@ -226,7 +226,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 		br_forward(READ_ONCE(dst->dst), skb, local_rcv, false);
 	} else {
 		if (!mcast_hit)
-			br_flood(br, skb, pkt_type, local_rcv, false, vid);
+			br_flood(br, vlan, skb, pkt_type, local_rcv, false);
 		else
 			br_multicast_flood(mdst, skb, brmctx, local_rcv, false);
 	}
diff --git a/net/bridge/br_mst.c b/net/bridge/br_mst.c
index 63fc1dd0e9bc0..252d5c927f716 100644
--- a/net/bridge/br_mst.c
+++ b/net/bridge/br_mst.c
@@ -79,16 +79,11 @@ int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state)
 }
 EXPORT_SYMBOL_GPL(br_mst_get_state);
 
-static void br_mst_vlan_set_state(struct net_bridge_vlan_group *vg,
-				  struct net_bridge_vlan *v,
-				  u8 state)
+static void br_mst_vlan_set_state(struct net_bridge_vlan *v, u8 state)
 {
 	if (br_vlan_get_state(v) == state)
 		return;
 
-	if (v->vid == br_get_pvid(vg))
-		br_vlan_set_pvid_state(vg, state);
-
 	br_vlan_set_state(v, state);
 }
 
@@ -129,7 +124,7 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state,
 		if (READ_ONCE(v->brvlan->msti) != msti)
 			continue;
 
-		br_mst_vlan_set_state(vg, v, state);
+		br_mst_vlan_set_state(v, state);
 	}
 
 out_rcu_unlock:
@@ -149,13 +144,13 @@ static void br_mst_vlan_sync_state(struct net_bridge_vlan *pv, u16 msti)
 		 * it.
 		 */
 		if (v != pv && v->brvlan->msti == msti) {
-			br_mst_vlan_set_state(vg, pv, br_vlan_get_state(v));
+			br_mst_vlan_set_state(pv, br_vlan_get_state(v));
 			return;
 		}
 	}
 
 	/* Otherwise, start out in a new MSTI with all ports disabled. */
-	return br_mst_vlan_set_state(vg, pv, BR_STATE_DISABLED);
+	return br_mst_vlan_set_state(pv, BR_STATE_DISABLED);
 }
 
 int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti)
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 09c397e303307..67117fb3dc880 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -190,6 +190,17 @@ enum {
 	BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED = BIT(6),
 };
 
+/* start publishing arrays when there're > BR_VLAN_PORT_ARRAY_THRESHOLD
+ * port-VLANs
+ */
+#define BR_VLAN_PORT_ARRAY_THRESHOLD 8
+
+struct net_bridge_vlan_port_array {
+	struct rcu_head		rcu;
+	unsigned int		count;
+	struct net_bridge_vlan	*vlans[];
+};
+
 /**
  * struct net_bridge_vlan - per-vlan entry
  *
@@ -210,6 +221,8 @@ enum {
  * @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast
  *                  context
  * @msti: if MASTER flag set, this holds the VLANs MST instance
+ * @port_array: if MASTER flag set, this is the port-VLAN array
+ * @port_vlist: if MASTER flag set, this is the port-VLAN list
  * @vlist: sorted list of VLAN entries
  * @rcu: used for entry destruction
  *
@@ -244,6 +257,8 @@ struct net_bridge_vlan {
 
 	u16				msti;
 
+	struct net_bridge_vlan_port_array __rcu *port_array;
+	struct list_head		port_vlist;
 	struct list_head		vlist;
 
 	struct rcu_head			rcu;
@@ -256,8 +271,7 @@ struct net_bridge_vlan {
  * @tunnel_hash: Hash table to map from tunnel key ID (e.g. VXLAN VNI) to VLAN
  * @vlan_list: sorted VLAN entry list
  * @num_vlans: number of total VLAN entries
- * @pvid: PVID VLAN id
- * @pvid_state: PVID's STP state (e.g. forwarding, learning, blocking)
+ * @pvid: RCU-protected PVID VLAN entry
  *
  * IMPORTANT: Be careful when checking if there're VLAN entries using list
  *            primitives because the bridge can have entries in its list which
@@ -269,9 +283,8 @@ struct net_bridge_vlan_group {
 	struct rhashtable		vlan_hash;
 	struct rhashtable		tunnel_hash;
 	struct list_head		vlan_list;
+	struct net_bridge_vlan		__rcu *pvid;
 	u16				num_vlans;
-	u16				pvid;
-	u8				pvid_state;
 };
 
 /* bridge fdb flags */
@@ -912,9 +925,9 @@ int br_dev_queue_push_xmit(struct net *net, struct sock *sk, struct sk_buff *skb
 void br_forward(const struct net_bridge_port *to, struct sk_buff *skb,
 		bool local_rcv, bool local_orig);
 int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
-void br_flood(struct net_bridge *br, struct sk_buff *skb,
-	      enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,
-	      u16 vid);
+void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,
+	      struct sk_buff *skb, enum br_pkt_type pkt_type,
+	      bool local_rcv, bool local_orig);
 
 /* return true if both source port and dest port are isolated */
 static inline bool br_skb_isolated(const struct net_bridge_port *to,
@@ -1593,6 +1606,7 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid);
 struct sk_buff *br_handle_vlan(struct net_bridge *br,
 			       const struct net_bridge_port *port,
 			       struct net_bridge_vlan_group *vg,
+			       struct net_bridge_vlan *vlan,
 			       struct sk_buff *skb);
 int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags,
 		bool *changed, struct netlink_ext_ack *extack);
@@ -1687,10 +1701,16 @@ static inline int br_vlan_get_tag(const struct sk_buff *skb, u16 *vid)
 
 static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)
 {
+	struct net_bridge_vlan *pvid;
+
 	if (!vg)
 		return 0;
 
-	return READ_ONCE(vg->pvid);
+	pvid = rcu_dereference_rtnl(vg->pvid);
+	if (!pvid)
+		return 0;
+
+	return pvid->vid;
 }
 
 static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
@@ -1724,6 +1744,7 @@ static inline bool br_should_learn(struct net_bridge_port *p,
 static inline struct sk_buff *br_handle_vlan(struct net_bridge *br,
 					     const struct net_bridge_port *port,
 					     struct net_bridge_vlan_group *vg,
+					     struct net_bridge_vlan *vlan,
 					     struct sk_buff *skb)
 {
 	return skb;
@@ -1922,17 +1943,6 @@ static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
 	br_multicast_update_vlan_mcast_ctx(v, state);
 }
 
-static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
-{
-	return READ_ONCE(vg->pvid_state);
-}
-
-static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,
-					  u8 state)
-{
-	WRITE_ONCE(vg->pvid_state, state);
-}
-
 /* learn_allow is true at ingress and false at egress */
 static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
 {
@@ -1945,6 +1955,11 @@ static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
 		return false;
 	}
 }
+#else
+static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
+{
+	return false;
+}
 #endif
 
 /* br_mst.c */
@@ -2375,6 +2390,8 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 		       u16 vid, struct net_bridge_port *p, struct nd_msg *msg);
 struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb);
-bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid);
-bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid);
+bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p,
+				  const struct net_bridge_vlan *v);
+bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p,
+				      const struct net_bridge_vlan *v);
 #endif
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index 34ede0f3e4523..ce5aa15c45402 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -35,21 +35,21 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
 }
 
 static void __vlan_add_pvid(struct net_bridge_vlan_group *vg,
-			    const struct net_bridge_vlan *v)
+			    struct net_bridge_vlan *v)
 {
-	if (vg->pvid == v->vid)
+	if (rcu_access_pointer(vg->pvid) == v)
 		return;
 
-	br_vlan_set_pvid_state(vg, br_vlan_get_state(v));
-	WRITE_ONCE(vg->pvid, v->vid);
+	RCU_INIT_POINTER(vg->pvid, v);
 }
 
-static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
+static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg,
+			       struct net_bridge_vlan *v)
 {
-	if (vg->pvid != vid)
+	if (rcu_access_pointer(vg->pvid) != v)
 		return;
 
-	WRITE_ONCE(vg->pvid, 0);
+	RCU_INIT_POINTER(vg->pvid, NULL);
 }
 
 /* Update the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED flags of @v.
@@ -60,6 +60,7 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
 				bool commit)
 {
 	struct net_bridge_vlan_group *vg;
+	struct net_bridge_vlan *pvid;
 	u16 vlan_flags;
 	bool change;
 
@@ -70,7 +71,8 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
 
 	/* check if anything would be changed on commit */
 	vlan_flags = v->flags;
-	change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(vg->pvid != v->vid) ||
+	pvid = rtnl_dereference(vg->pvid);
+	change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(pvid != v) ||
 		 ((flags ^ vlan_flags) & BRIDGE_VLAN_INFO_UNTAGGED);
 
 	if (!commit)
@@ -79,7 +81,7 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
 	if (flags & BRIDGE_VLAN_INFO_PVID)
 		__vlan_add_pvid(vg, v);
 	else
-		__vlan_delete_pvid(vg, v->vid);
+		__vlan_delete_pvid(vg, v);
 
 	if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
 		vlan_flags |= BRIDGE_VLAN_INFO_UNTAGGED;
@@ -128,10 +130,12 @@ static void __vlan_add_list(struct net_bridge_vlan *v)
 	struct list_head *headp, *hpos;
 	struct net_bridge_vlan *vent;
 
-	if (br_vlan_is_master(v))
+	if (br_vlan_is_master(v)) {
 		vg = br_vlan_group(v->br);
-	else
+	} else {
 		vg = nbp_vlan_group(v->port);
+		list_add_rcu(&v->port_vlist, &v->brvlan->port_vlist);
+	}
 
 	headp = &vg->vlan_list;
 	list_for_each_prev(hpos, headp) {
@@ -145,6 +149,10 @@ static void __vlan_add_list(struct net_bridge_vlan *v)
 static void __vlan_del_list(struct net_bridge_vlan *v)
 {
 	list_del_rcu(&v->vlist);
+	if (br_vlan_is_master(v))
+		WARN_ON(!list_empty(&v->port_vlist));
+	else
+		list_del_rcu(&v->port_vlist);
 }
 
 static int __vlan_vid_del(struct net_device *dev, struct net_bridge *br,
@@ -250,6 +258,36 @@ static void br_vlan_init_state(struct net_bridge_vlan *v)
 	v->msti = 0;
 }
 
+static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)
+{
+	return refcount_read(&masterv->refcnt) - br_vlan_is_brentry(masterv);
+}
+
+static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,
+				       unsigned int count)
+{
+	struct net_bridge_vlan_port_array *array = NULL, *old;
+	unsigned int i = 0;
+
+	WARN_ON(!br_vlan_is_master(masterv));
+
+	if (count > BR_VLAN_PORT_ARRAY_THRESHOLD)
+		array = kvmalloc(struct_size(array, vlans, count), GFP_KERNEL);
+
+	if (array) {
+		struct net_bridge_vlan *pv;
+
+		array->count = count;
+		list_for_each_entry(pv, &masterv->port_vlist, port_vlist)
+			array->vlans[i++] = pv;
+	}
+
+	old = rtnl_dereference(masterv->port_array);
+	rcu_assign_pointer(masterv->port_array, array);
+	if (old)
+		kvfree_rcu(old, rcu);
+}
+
 /* This is the shared VLAN add function which works for both ports and bridge
  * devices. There are four possible calls to this function in terms of the
  * vlan entry type:
@@ -333,6 +371,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
 		}
 		br_multicast_ctx_init(br, v, &v->br_mcast_ctx);
 		v->priv_flags |= BR_VLFLAG_GLOBAL_MCAST_ENABLED;
+		INIT_LIST_HEAD(&v->port_vlist);
 	}
 
 	/* Add the dev mac and count the vlan only if it's usable */
@@ -359,8 +398,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
 	__vlan_flags_commit(v, flags);
 	br_multicast_toggle_one_vlan(v, true);
 
-	if (p)
+	if (p) {
+		br_vlan_rebuild_port_array(masterv, br_vlan_num_ports(masterv));
 		nbp_vlan_set_vlan_dev_state(p, v->vid);
+	}
 out:
 	return err;
 
@@ -403,7 +444,7 @@ static void __vlan_del(struct net_bridge_vlan *v)
 		masterv = v->brvlan;
 	}
 
-	__vlan_delete_pvid(vg, v->vid);
+	__vlan_delete_pvid(vg, v);
 	if (p) {
 		err = __vlan_vid_del(p->dev, p->br, v);
 		if (err)
@@ -429,6 +470,9 @@ static void __vlan_del(struct net_bridge_vlan *v)
 		rhashtable_remove_fast(&vg->vlan_hash, &v->vnode,
 				       br_vlan_rht_params);
 		__vlan_del_list(v);
+		/* -1 because br_vlan_put_master() is called later */
+		br_vlan_rebuild_port_array(masterv,
+					   br_vlan_num_ports(masterv) - 1);
 		nbp_vlan_set_vlan_dev_state(p, v->vid);
 		br_multicast_toggle_one_vlan(v, false);
 		br_multicast_port_ctx_deinit(&v->port_mcast_ctx);
@@ -453,7 +497,7 @@ static void __vlan_flush(const struct net_bridge *br,
 	struct net_bridge_vlan *vlan, *tmp;
 	u16 v_start = 0, v_end = 0;
 
-	__vlan_delete_pvid(vg, vg->pvid);
+	__vlan_delete_pvid(vg, rtnl_dereference(vg->pvid));
 	list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) {
 		/* take care of disjoint ranges */
 		if (!v_start) {
@@ -476,10 +520,10 @@ static void __vlan_flush(const struct net_bridge *br,
 struct sk_buff *br_handle_vlan(struct net_bridge *br,
 			       const struct net_bridge_port *p,
 			       struct net_bridge_vlan_group *vg,
+			       struct net_bridge_vlan *v,
 			       struct sk_buff *skb)
 {
 	struct pcpu_sw_netstats *stats;
-	struct net_bridge_vlan *v;
 	u16 vid;
 
 	/* If this packet was not filtered at input, let it pass */
@@ -490,19 +534,22 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
 	 * a valid vlan id.  If the vlan id has untagged flag set,
 	 * send untagged; otherwise, send tagged.
 	 */
-	br_vlan_get_tag(skb, &vid);
-	v = br_vlan_find(vg, vid);
-	/* Vlan entry must be configured at this point.  The
-	 * only exception is the bridge is set in promisc mode and the
-	 * packet is destined for the bridge device.  In this case
-	 * pass the packet as is.
-	 */
-	if (!v || !br_vlan_should_use(v)) {
-		if ((br->dev->flags & IFF_PROMISC) && skb->dev == br->dev) {
-			goto out;
-		} else {
-			kfree_skb(skb);
-			return NULL;
+	if (!v) {
+		br_vlan_get_tag(skb, &vid);
+		v = br_vlan_find(vg, vid);
+		/* Vlan entry must be configured at this point.  The
+		 * only exception is the bridge is set in promisc mode and the
+		 * packet is destined for the bridge device.  In this case
+		 * pass the packet as is.
+		 */
+		if (!v || !br_vlan_should_use(v)) {
+			if ((br->dev->flags & IFF_PROMISC) &&
+			    skb->dev == br->dev) {
+				goto out;
+			} else {
+				kfree_skb(skb);
+				return NULL;
+			}
 		}
 	}
 	if (br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
@@ -579,42 +626,32 @@ static bool __allowed_ingress(const struct net_bridge *br,
 	}
 
 	if (!*vid) {
-		u16 pvid = br_get_pvid(vg);
-
+		v = vg ? rcu_dereference(vg->pvid) : NULL;
 		/* Frame had a tag with VID 0 or did not have a tag.
 		 * See if pvid is set on this port.  That tells us which
 		 * vlan untagged or priority-tagged traffic belongs to.
 		 */
-		if (!pvid)
+		if (!v)
 			goto drop;
 
 		/* PVID is set on this port.  Any untagged or priority-tagged
 		 * ingress frame is considered to belong to this vlan.
 		 */
-		*vid = pvid;
+		*vid = v->vid;
 		if (likely(!tagged))
 			/* Untagged Frame. */
-			__vlan_hwaccel_put_tag(skb, br->vlan_proto, pvid);
+			__vlan_hwaccel_put_tag(skb, br->vlan_proto, v->vid);
 		else
 			/* Priority-tagged Frame.
 			 * At this point, we know that skb->vlan_tci VID
 			 * field was 0.
 			 * We update only VID field and preserve PCP field.
 			 */
-			skb->vlan_tci |= pvid;
-
-		/* if snooping and stats are disabled we can avoid the lookup */
-		if (!br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
-		    !br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
-			if (*state == BR_STATE_FORWARDING) {
-				*state = br_vlan_get_pvid_state(vg);
-				if (!br_vlan_state_allowed(*state, true))
-					goto drop;
-			}
-			return true;
-		}
+			skb->vlan_tci |= v->vid;
+	} else {
+		v = br_vlan_find(vg, *vid);
 	}
-	v = br_vlan_find(vg, *vid);
+
 	if (!v || !br_vlan_should_use(v))
 		goto drop;
 
@@ -697,11 +734,10 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)
 		*vid = 0;
 
 	if (!*vid) {
-		*vid = br_get_pvid(vg);
-		if (!*vid ||
-		    !br_vlan_state_allowed(br_vlan_get_pvid_state(vg), true))
+		v = rcu_dereference(vg->pvid);
+		if (!v || !br_vlan_state_allowed(br_vlan_get_state(v), true))
 			return false;
-
+		*vid = v->vid;
 		return true;
 	}
 
@@ -1061,17 +1097,10 @@ int br_vlan_set_stats_per_port(struct net_bridge *br, unsigned long val)
 
 static bool vlan_default_pvid(struct net_bridge_vlan_group *vg, u16 vid)
 {
-	struct net_bridge_vlan *v;
+	struct net_bridge_vlan *pvid = rtnl_dereference(vg->pvid);
 
-	if (vid != vg->pvid)
-		return false;
-
-	v = br_vlan_lookup(&vg->vlan_hash, vid);
-	if (v && br_vlan_should_use(v) &&
-	    (v->flags & BRIDGE_VLAN_INFO_UNTAGGED))
-		return true;
-
-	return false;
+	return pvid && pvid->vid == vid && br_vlan_should_use(pvid) &&
+	       (pvid->flags & BRIDGE_VLAN_INFO_UNTAGGED);
 }
 
 static void br_vlan_disable_default_pvid(struct net_bridge *br)
@@ -1524,7 +1553,7 @@ int br_vlan_get_info(const struct net_device *dev, u16 vid,
 
 	p_vinfo->vid = vid;
 	p_vinfo->flags = v->flags;
-	if (vid == br_get_pvid(vg))
+	if (v == rcu_access_pointer(vg->pvid))
 		p_vinfo->flags |= BRIDGE_VLAN_INFO_PVID;
 	return 0;
 }
@@ -1551,7 +1580,7 @@ int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
 
 	p_vinfo->vid = vid;
 	p_vinfo->flags = READ_ONCE(v->flags);
-	if (vid == br_get_pvid(vg))
+	if (v == rcu_access_pointer(vg->pvid))
 		p_vinfo->flags |= BRIDGE_VLAN_INFO_PVID;
 	return 0;
 }
@@ -1956,7 +1985,7 @@ void br_vlan_notify(const struct net_bridge *br,
 			goto out_kfree;
 
 		flags = v->flags;
-		if (br_get_pvid(vg) == v->vid)
+		if (v == rcu_access_pointer(vg->pvid))
 			flags |= BRIDGE_VLAN_INFO_PVID;
 		break;
 	case RTM_DELVLAN:
diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
index 506668fc31d9d..e4b7a147e42b2 100644
--- a/net/bridge/br_vlan_options.c
+++ b/net/bridge/br_vlan_options.c
@@ -110,8 +110,7 @@ size_t br_vlan_opts_nl_size(void)
 	       + 0;
 }
 
-static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
-				struct net_bridge_vlan *v,
+static int br_vlan_modify_state(struct net_bridge_vlan *v,
 				u8 state,
 				bool *changed,
 				struct netlink_ext_ack *extack)
@@ -143,9 +142,6 @@ static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
 	if (v->state == state)
 		return 0;
 
-	if (v->vid == br_get_pvid(vg))
-		br_vlan_set_pvid_state(vg, state);
-
 	br_vlan_set_state(v, state);
 	*changed = true;
 
@@ -216,7 +212,6 @@ static int br_vlan_modify_tunnel(const struct net_bridge_port *p,
 
 static int br_vlan_process_one_opts(const struct net_bridge *br,
 				    const struct net_bridge_port *p,
-				    struct net_bridge_vlan_group *vg,
 				    struct net_bridge_vlan *v,
 				    struct nlattr **tb,
 				    bool *changed,
@@ -228,7 +223,7 @@ static int br_vlan_process_one_opts(const struct net_bridge *br,
 	if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
 		u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
 
-		err = br_vlan_modify_state(vg, v, state, changed, extack);
+		err = br_vlan_modify_state(v, state, changed, extack);
 		if (err)
 			return err;
 	}
@@ -339,8 +334,7 @@ int br_vlan_process_options(const struct net_bridge *br,
 			break;
 		}
 
-		err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
-					       extack);
+		err = br_vlan_process_one_opts(br, p, v, tb, &changed, extack);
 		if (err)
 			break;
 

3/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 18745
							output: 25
							thoughts: 1263
						

Thoughts:
**Patch Analysis: Bridging and VLAN Optimization**

Okay, let's break this patch down. My initial assessment is that it's focusing on optimizing VLAN handling within the Linux bridge code. I'm seeing modifications across several key files in `net/bridge/`, which immediately suggests a core networking change.

First, I need to understand the intent. Since I don't have access to a `git log` tool, I'll need to rely on the patch itself and any commit message available. The author is listed as a syz-cluster, so it's likely a fuzzing-related patch.

Now, scrutinizing the diff, it appears this patch is introducing a new `net_bridge_vlan_port_array` structure. This array seems to be designed for storing VLAN port information, and I see it's RCU-protected. This indicates a potential move toward more efficient lookup and modification of VLAN-to-port mappings, especially under concurrency. The threshold `BR_VLAN_PORT_ARRAY_THRESHOLD` (set to 8) hints at a performance optimization: switching to an array-based representation when a certain number of VLAN ports are associated with a bridge. This could improve lookup speeds compared to a linked list.

The changes to `br_forward.c` and specifically `br_flood` are key. The function signature has changed to accept a `net_bridge_vlan` pointer, and the internal logic now iterates over this array or uses the `port_vlist` if no array is present. The creation of `struct br_fwd_dst dst` with the updated arguments, and `should_deliver_vlan` indicate that forwarding decisions are now being made with per-VLAN context.

The changes in `br_vlan.c` reinforce this VLAN-centric approach. Functions like `__vlan_add_pvid`, `__vlan_delete_pvid`, `__vlan_add_list`, and `__vlan_del_list` are all now operating on `net_bridge_vlan` structures. Assertions (`WARN_ON`) are added, which are always interesting from a vulnerability perspective, I see one in `__vlan_del_list` which will fail if a vlan port list is not empty before deletion, and another that requires that the vlan is a master before the rebuild array function is invoked.

The modifications to `br_arp_nd_proxy.c` show that VLAN context is now being passed to ARP/ND proxy functions, suggesting that the proxying behavior is now VLAN-aware.

From an execution and testing perspective, this code is absolutely reachable. Bridges are standard Linux networking components. This can be tested using `ip link add type bridge` as syzkaller does, so it's a prime target for fuzzing. The patch alters core logic, memory management, RCU lists and arrays, and locking. It adds dynamic memory allocation with `kvmalloc` and `kvfree_rcu` and modifies assertions. Given all of this, this patch is *definitely* worth fuzzing.

Now, let's verify that Kconfig requirements are met before submitting the patch for more detailed analysis.



4/2 2026/09/20 21:30 tool grepper
0m Args:
{
  "Expression": "config BRIDGE",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/Kconfig-5-\nnet/bridge/Kconfig:6:config BRIDGE\nnet/bridge/Kconfig-7-\ttristate \"802.1d Ethernet Bridging\"\n--\nnet/bridge/Kconfig-35-\nnet/bridge/Kconfig:36:config BRIDGE_IGMP_SNOOPING\nnet/bridge/Kconfig-37-\tbool \"IGMP/MLD snooping\"\n--\nnet/bridge/Kconfig-49-\nnet/bridge/Kconfig:50:config BRIDGE_VLAN_FILTERING\nnet/bridge/Kconfig-51-\tbool \"VLAN filtering\"\n--\nnet/bridge/Kconfig-63-\nnet/bridge/Kconfig:64:config BRIDGE_MRP\nnet/bridge/Kconfig-65-\tbool \"MRP protocol\"\n--\nnet/bridge/Kconfig-75-\nnet/bridge/Kconfig:76:config BRIDGE_CFM\nnet/bridge/Kconfig-77-\tbool \"CFM protocol\"\n--\nnet/bridge/netfilter/Kconfig=28=config NF_CONNTRACK_BRIDGE\n--\nnet/bridge/netfilter/Kconfig-42-# old sockopt interface and eval loop\nnet/bridge/netfilter/Kconfig:43:config BRIDGE_NF_EBTABLES_LEGACY\nnet/bridge/netfilter/Kconfig-44-\ttristate \"Legacy EBTABLES support\"\n--\nnet/bridge/netfilter/Kconfig-52-\nnet/bridge/netfilter/Kconfig:53:menuconfig BRIDGE_NF_EBTABLES\nnet/bridge/netfilter/Kconfig-54-\ttristate \"Ethernet Bridge tables (ebtables) support\"\n--\nnet/bridge/netfilter/Kconfig=62=if BRIDGE_NF_EBTABLES\n--\nnet/bridge/netfilter/Kconfig-66-#\nnet/bridge/netfilter/Kconfig:67:config BRIDGE_EBT_BROUTE\nnet/bridge/netfilter/Kconfig-68-\ttristate \"ebt: broute table support\"\n--\nnet/bridge/netfilter/Kconfig-77-\nnet/bridge/netfilter/Kconfig:78:config BRIDGE_EBT_T_FILTER\nnet/bridge/netfilter/Kconfig-79-\ttristate \"ebt: filter table support\"\n--\nnet/bridge/netfilter/Kconfig-87-\nnet/bridge/netfilter/Kconfig:88:config BRIDGE_EBT_T_NAT\nnet/bridge/netfilter/Kconfig-89-\ttristate \"ebt: nat table support\"\n--\nnet/bridge/netfilter/Kconfig-99-#\nnet/bridge/netfilter/Kconfig:100:config BRIDGE_EBT_802_3\nnet/bridge/netfilter/Kconfig-101-\ttristate \"ebt: 802.3 filter support\"\n--\nnet/bridge/netfilter/Kconfig-106-\nnet/bridge/netfilter/Kconfig:107:config BRIDGE_EBT_AMONG\nnet/bridge/netfilter/Kconfig-108-\ttristate \"ebt: among filter support\"\n--\nnet/bridge/netfilter/Kconfig-115-\nnet/bridge/netfilter/Kconfig:116:config BRIDGE_EBT_ARP\nnet/bridge/netfilter/Kconfig-117-\ttristate \"ebt: ARP filter support\"\n--\nnet/bridge/netfilter/Kconfig-123-\nnet/bridge/netfilter/Kconfig:124:config BRIDGE_EBT_IP\nnet/bridge/netfilter/Kconfig-125-\ttristate \"ebt: IP filter support\"\n--\nnet/bridge/netfilter/Kconfig-131-\nnet/bridge/netfilter/Kconfig:132:config BRIDGE_EBT_IP6\nnet/bridge/netfilter/Kconfig-133-\ttristate \"ebt: IP6 filter support\"\n--\nnet/bridge/netfilter/Kconfig-140-\nnet/bridge/netfilter/Kconfig:141:config BRIDGE_EBT_LIMIT\nnet/bridge/netfilter/Kconfig-142-\ttristate \"ebt: limit match support\"\n--\nnet/bridge/netfilter/Kconfig-150-\nnet/bridge/netfilter/Kconfig:151:config BRIDGE_EBT_MARK\nnet/bridge/netfilter/Kconfig-152-\ttristate \"ebt: mark filter support\"\n--\nnet/bridge/netfilter/Kconfig-160-\nnet/bridge/netfilter/Kconfig:161:config BRIDGE_EBT_PKTTYPE\nnet/bridge/netfilter/Kconfig-162-\ttristate \"ebt: packet type filter support\"\n--\nnet/bridge/netfilter/Kconfig-170-\nnet/bridge/netfilter/Kconfig:171:config BRIDGE_EBT_STP\nnet/bridge/netfilter/Kconfig-172-\ttristate \"ebt: STP filter support\"\n--\nnet/bridge/netfilter/Kconfig-178-\nnet/bridge/netfilter/Kconfig:179:config BRIDGE_EBT_VLAN\nnet/bridge/netfilter/Kconfig-180-\ttristate \"ebt: 802.1Q VLAN filter support\"\n--\nnet/bridge/netfilter/Kconfig-188-#\nnet/bridge/netfilter/Kconfig:189:config BRIDGE_EBT_ARPREPLY\nnet/bridge/netfilter/Kconfig-190-\ttristate \"ebt: arp reply target support\"\n--\nnet/bridge/netfilter/Kconfig-197-\nnet/bridge/netfilter/Kconfig:198:config BRIDGE_EBT_DNAT\nnet/bridge/netfilter/Kconfig-199-\ttristate \"ebt: dnat target support\"\n--\nnet/bridge/netfilter/Kconfig-205-\nnet/bridge/netfilter/Kconfig:206:config BRIDGE_EBT_MARK_T\nnet/bridge/netfilter/Kconfig-207-\ttristate \"ebt: mark target support\"\n--\nnet/bridge/netfilter/Kconfig-215-\nnet/bridge/netfilter/Kconfig:216:config BRIDGE_EBT_REDIRECT\nnet/bridge/netfilter/Kconfig-217-\ttristate \"ebt: redirect target support\"\n--\nnet/bridge/netfilter/Kconfig-223-\nnet/bridge/netfilter/Kconfig:224:config BRIDGE_EBT_SNAT\nnet/bridge/netfilter/Kconfig-225-\ttristate \"ebt: snat target support\"\n--\nnet/bridge/netfilter/Kconfig-233-#\nnet/bridge/netfilter/Kconfig:234:config BRIDGE_EBT_LOG\nnet/bridge/netfilter/Kconfig-235-\ttristate \"ebt: log support\"\n--\nnet/bridge/netfilter/Kconfig-242-\nnet/bridge/netfilter/Kconfig:243:config BRIDGE_EBT_NFLOG\nnet/bridge/netfilter/Kconfig-244-\ttristate \"ebt: nflog support\"\n"
}

5/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22505
							output: 31
							thoughts: 18
						

Thoughts:
Here's my take on those thoughts:

**Examining the Bridge Makefile for Build Process Insights**

Okay, so I'm thinking I need to understand exactly how the bridging code in the kernel is built. The most logical place to start is the `net/bridge/Makefile`. That file should give me the lowdown on the compilation process, how the source files are linked, any special compiler flags being used, and dependencies. It'll probably reveal the intricacies of how the bridge module is structured from a build perspective, which will give me valuable context for any modifications or debugging I might need to do down the line. It's the starting point for really getting my hands dirty with this particular subsystem.



6/2 2026/09/20 21:30 tool read-file
0m Args:
{
  "File": "net/bridge/Makefile",
  "FirstLine": 1,
  "LineCount": 40
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0\n   2:\t#\n   3:\t# Makefile for the IEEE 802.1d ethernet bridging layer.\n   4:\t#\n   5:\t\n   6:\tobj-$(CONFIG_BRIDGE) += bridge.o\n   7:\t\n   8:\tbridge-y\t:= br.o br_device.o br_fdb.o br_forward.o br_if.o br_input.o \\\n   9:\t\t\t\tbr_ioctl.o br_stp.o br_stp_bpdu.o \\\n  10:\t\t\t\tbr_stp_if.o br_stp_timer.o br_netlink.o \\\n  11:\t\t\t\tbr_netlink_tunnel.o br_arp_nd_proxy.o\n  12:\t\n  13:\tbridge-$(CONFIG_SYSFS) += br_sysfs_if.o br_sysfs_br.o\n  14:\t\n  15:\tbridge-$(subst m,y,$(CONFIG_BRIDGE_NETFILTER)) += br_nf_core.o\n  16:\t\n  17:\tbr_netfilter-y := br_netfilter_hooks.o\n  18:\tbr_netfilter-$(subst m,y,$(CONFIG_IPV6)) += br_netfilter_ipv6.o\n  19:\tobj-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o\n  20:\t\n  21:\tbridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o br_multicast_eht.o\n  22:\t\n  23:\tbridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o br_mst.o\n  24:\t\n  25:\tbridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o\n  26:\t\n  27:\tobj-$(CONFIG_NETFILTER) += netfilter/\n  28:\t\n  29:\tbridge-$(CONFIG_BRIDGE_MRP)\t+= br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o\n  30:\t\n  31:\tbridge-$(CONFIG_BRIDGE_CFM)\t+= br_cfm.o br_cfm_netlink.o\n  32:\t\n  33:\tifdef CONFIG_GCOV_PROFILE_NETFILTER\n  34:\tGCOV_PROFILE_br_nf_core.o := y\n  35:\tGCOV_PROFILE_br_netfilter_hooks.o := y\n  36:\tGCOV_PROFILE_br_netfilter_ipv6.o := y\n  37:\tendif\n"
}

7/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 23395
							output: 33
							thoughts: 222
						

Thoughts:
**Examining the Linux Bridge VLAN Filtering Configuration**

Okay, let's dive into the bridge VLAN filtering implementation. I'm looking at the `net/bridge/Makefile` and immediately notice some key dependencies. Line 23 is interesting: `bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o br_mst.o`. That tells me that the compilation of the VLAN filtering modules hinges on `CONFIG_BRIDGE_VLAN_FILTERING`. Now, going up to line 6, we see `obj-$(CONFIG_BRIDGE) += bridge.o` and then `bridge-y := br.o br_device.o br_fdb.o br_forward.o br_if.o br_input.o br_ioctl.o br_stp.o br_stp_bpdu.o br_stp_if.o br_stp_timer.o br_netlink.o br_netlink_tunnel.o br_arp_nd_proxy.o` on line 8, which indicates that the base bridge functionality depends on `CONFIG_BRIDGE`. Clearly, both `CONFIG_BRIDGE` and `CONFIG_BRIDGE_VLAN_FILTERING` are essential for a complete VLAN filtering implementation. My next step will be to examine the `net/bridge/Kconfig` file to understand the configuration options for `BRIDGE_VLAN_FILTERING` in detail. This will provide valuable context around what the user is presented with.



8/2 2026/09/20 21:30 tool read-file
0m Args:
{
  "File": "net/bridge/Kconfig",
  "FirstLine": 45,
  "LineCount": 25
}

Results:
{
  "Contents": "  45:\t\n  46:\t\t  Say N to exclude this support and reduce the binary size.\n  47:\t\n  48:\t\t  If unsure, say Y.\n  49:\t\n  50:\tconfig BRIDGE_VLAN_FILTERING\n  51:\t\tbool \"VLAN filtering\"\n  52:\t\tdepends on BRIDGE\n  53:\t\tdepends on VLAN_8021Q\n  54:\t\tdefault n\n  55:\t\thelp\n  56:\t\t  If you say Y here, then the Ethernet bridge will be able selectively\n  57:\t\t  receive and forward traffic based on VLAN information in the packet\n  58:\t\t  any VLAN information configured on the bridge port or bridge device.\n  59:\t\n  60:\t\t  Say N to exclude this support and reduce the binary size.\n  61:\t\n  62:\t\t  If unsure, say Y.\n  63:\t\n  64:\tconfig BRIDGE_MRP\n  65:\t\tbool \"MRP protocol\"\n  66:\t\tdepends on BRIDGE\n  67:\t\tdefault n\n  68:\t\thelp\n  69:\t\t  If you say Y here, then the Ethernet bridge will be able to run MRP\n"
}

9/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 24285
							output: 31
							thoughts: 1007
						

Thoughts:
**Analyzing the Code Changes**

Okay, so I'm looking at this patch and immediately I need to understand its dependencies. `CONFIG_BRIDGE_VLAN_FILTERING` relies on `BRIDGE` and `VLAN_8021Q`, that's straightforward. Then I see a reference to `CONFIG_INET` in `br_arp_nd_proxy.c`, specifically with `#if IS_ENABLED(CONFIG_INET)`. Let's confirm - yeah, `br_do_proxy_suppress_arp` and `br_do_suppress_nd` definitely require `INET`. Next, I'm checking `CONFIG_BRIDGE_IGMP_SNOOPING` because I see `br_multicast_flood` in `br_forward.c` uses it.

Now, I'm diving into the meat of the changes: the modified and added functions. I need to get a handle on the scope of impact.

I have a list of modified files, let's go file by file.

In `net/bridge/br_arp_nd_proxy.c`, there's a bunch of stuff. `br_is_neigh_suppress_enabled_vid`, `br_is_neigh_suppress_enabled`, `br_is_neigh_forward_grat_enabled`, `br_do_proxy_suppress_arp`, and `br_do_suppress_nd`.

`net/bridge/br_device.c` has `br_dev_xmit` changes.

`net/bridge/br_forward.c` has a lot. `should_deliver_vlan`, `should_deliver`, `__br_forward`, `deliver_clone`, `br_forward`, `maybe_deliver`, `br_flood_finish`, `br_flood_port`, `br_flood_vlan`, `br_flood`, and `br_multicast_flood` which is conditional on `CONFIG_BRIDGE_IGMP_SNOOPING`.

`net/bridge/br_input.c` has `br_pass_frame_up` and `br_handle_frame_finish`.

`net/bridge/br_mst.c` has `br_mst_vlan_set_state`, `br_mst_set_state`, and `br_mst_vlan_sync_state`.

`net/bridge/br_vlan.c` is extensive. There is `__vlan_add_pvid`, `__vlan_delete_pvid`, `__vlan_flags_update`, `__vlan_add_list`, `__vlan_del_list`, `br_vlan_num_ports`, `br_vlan_rebuild_port_array`, `__vlan_add`, `__vlan_del`, `__vlan_flush`, `br_handle_vlan`, `__allowed_ingress`, `br_should_learn`, `vlan_default_pvid`, `br_vlan_get_info`, `br_vlan_get_info_rcu`, and `br_vlan_notify`.

Lastly, `net/bridge/br_vlan_options.c`, has `br_vlan_modify_state`, `br_vlan_process_one_opts`, and `br_vlan_process_options`.

Okay, I need to understand which of these functions are actually going to be entry points, or, at least, have external visibility. I'm going to check which functions are non-static, important symbols, exported or not inlined. I need to check `nm vmlinux` to find distinct symbols. I'll focus on `br_flood`, `br_flood_vlan`, `br_vlan_rebuild_port_array` etc. to see if they are actually static or not.

I'll check the callers of `br_vlan_rebuild_port_array` to understand how it's used. Okay, in `br_vlan.c`, `__vlan_add` calls it. And `__vlan_add` is called by `br_vlan_add`, which is an exported symbol or is, at least, called by a netlink interface. I need to examine `br_vlan_add` in `br_vlan.c` more closely now.



10/2 2026/09/20 21:30 tool grepper
0m Args:
{
  "Expression": "^int br_vlan_add",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_private.h=1606=struct sk_buff *br_handle_vlan(struct net_bridge *br,\n--\nnet/bridge/br_private.h-1610-\t\t\t       struct sk_buff *skb);\nnet/bridge/br_private.h:1611:int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags,\nnet/bridge/br_private.h-1612-\t\tbool *changed, struct netlink_ext_ack *extack);\n--\nnet/bridge/br_vlan.c=751=static int br_vlan_add_existing(struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-810- */\nnet/bridge/br_vlan.c:811:int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,\nnet/bridge/br_vlan.c-812-\t\tstruct netlink_ext_ack *extack)\n"
}

11/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26553
							output: 29
							thoughts: 63
						

Thoughts:
**Investigating VLAN Tagging in Bridged Networks**

Okay, so I need to get a handle on how VLAN filtering and addition are handled within the bridging code. First, I'll zero in on `br_vlan_filter_toggle` and see how it interacts with the VLAN addition functions, specifically `br_vlan_add` and `nbp_vlan_add`. Those are the key players here. To start, I'll do a quick `grep` for `__vlan_add(` within `net/bridge/br_vlan.c`. That should give me a good overview of where those core functions are being called and, by extension, how the VLAN tagging process is orchestrated. Time to dive in and see what the code reveals.



12/2 2026/09/20 21:30 tool grepper
0m Args:
{
  "Expression": "__vlan_add\\(",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=266=static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,\n--\nnet/bridge/br_vlan.c-301- */\nnet/bridge/br_vlan.c:302:static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\nnet/bridge/br_vlan.c-303-\t\t      struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c=811=int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,\n--\nnet/bridge/br_vlan.c-841-\t\trefcount_set(\u0026vlan-\u003erefcnt, 1);\nnet/bridge/br_vlan.c:842:\tret = __vlan_add(vlan, flags, extack);\nnet/bridge/br_vlan.c-843-\tif (ret) {\n--\nnet/bridge/br_vlan.c=1341=int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags,\n--\nnet/bridge/br_vlan.c-1373-\tvlan-\u003eport = port;\nnet/bridge/br_vlan.c:1374:\tret = __vlan_add(vlan, flags, extack);\nnet/bridge/br_vlan.c-1375-\tif (ret)\n"
}

13/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27212
							output: 29
							thoughts: 63
						

Thoughts:
**VLAN Management: A Quick Check**

Okay, so I'm thinking about VLAN management, specifically the functions `nbp_vlan_add` and `br_vlan_add`. These are the key functions for adding VLANs in this context. And of course, there are their corresponding delete functions: `nbp_vlan_delete` and `br_vlan_delete`. They both call `__vlan_del`, which is the core function for deletion. Since I'm thinking about the delete process, my immediate thought is to trace the call to `__vlan_del`. I want to quickly verify where this particular function is also invoked, perhaps to better understand the VLAN deletion procedure.



14/2 2026/09/20 21:30 tool grepper
0m Args:
{
  "Expression": "__vlan_del\\(",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=302=static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-431-\nnet/bridge/br_vlan.c:432:static void __vlan_del(struct net_bridge_vlan *v)\nnet/bridge/br_vlan.c-433-{\n--\nnet/bridge/br_vlan.c=493=static void __vlan_flush(const struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-511-\nnet/bridge/br_vlan.c:512:\t\t__vlan_del(vlan);\nnet/bridge/br_vlan.c-513-\t}\n--\nnet/bridge/br_vlan.c=856=int br_vlan_delete(struct net_bridge *br, u16 vid)\n--\nnet/bridge/br_vlan.c-871-\tvlan_tunnel_info_del(vg, v);\nnet/bridge/br_vlan.c:872:\t__vlan_del(v);\nnet/bridge/br_vlan.c-873-\n--\nnet/bridge/br_vlan.c=1386=int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)\n--\nnet/bridge/br_vlan.c-1396-\tbr_fdb_delete_by_port(port-\u003ebr, port, vid, 0);\nnet/bridge/br_vlan.c:1397:\t__vlan_del(v);\nnet/bridge/br_vlan.c-1398-\n"
}

15/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27908
							output: 29
							thoughts: 1928
						

Thoughts:
**Focus Symbol Determination: VLAN-Aware Bridging**

Okay, so I've been digging into this bridge code, specifically looking at how VLANs are handled and where the critical functions reside. I'm focusing on those that are either new, heavily modified, or act as key entrypoints for the VLAN-aware bridging features.

Initially, I zeroed in on `br_vlan_rebuild_port_array` because that's where the port array optimization kicks in, triggered by `__vlan_add` and `__vlan_del` when the port count exceeds the threshold. This port array is built for fast lookup during `br_flood_vlan`, which, in turn, is used by `br_flood`. The whole point of that array is to iterate over the `vlans[i]` (when it exists) to flood on certain ports or iterate over `port_vlist`.

After that, I traced the call graph through `br_flood`, which is called from `br_dev_xmit` and `br_handle_frame_finish`, and `br_handle_vlan`, called from `br_pass_frame_up` and `__br_forward`. And of course `br_forward` is called from `br_handle_frame_finish` and `br_dev_xmit`.

My focus is on extracting specific feature logic. Given this, I made a list of functions: `br_flood`, `br_flood_vlan` (which is static but called from `br_flood`), `br_vlan_rebuild_port_array`, `br_handle_vlan`, `br_is_neigh_suppress_enabled`, `br_is_neigh_forward_grat_enabled`, `br_mst_set_state`, `br_vlan_process_options`, `br_vlan_add`, `nbp_vlan_add`, `br_vlan_delete`, `nbp_vlan_delete`, `br_multicast_flood` and `br_forward`.

The thing is, I had to decide whether certain functions were too generic. For instance, `br_dev_xmit` is *everywhere* for a bridge, but it is specific to the bridge subsystem. However, `br_flood` is more focused because its signature and implementation were changed and now calls `br_flood_vlan`, etc..

So, the core functions I'm targeting are:

*   `br_flood`: A key function in the VLAN-aware flooding with its call to `br_flood_vlan`.
*   `br_flood_vlan`: The new core of VLAN-aware flooding using the `port_array` and `port_vlist`.
*   `br_vlan_rebuild_port_array`: Because it is key to managing that lookup data structure.
*   `br_handle_vlan`: Modified signature and now looks up bypass when `vlan` is provided.
*   `br_is_neigh_suppress_enabled`: Modified signature.
*   `br_is_neigh_forward_grat_enabled`: Modified signature.
*   `nbp_vlan_add` and `nbp_vlan_delete`: because they trigger array rebuilds.
*   `br_mst_set_state`: Also modified.
*   `br_vlan_process_options`: Also modified.
*   `br_multicast_flood`: It was modified too.

Lastly, I had to ensure that the configuration dependencies were correct. Looks like we need `BRIDGE`, `BRIDGE_VLAN_FILTERING`, and `VLAN_8021Q`. And, for multicast, `CONFIG_BRIDGE_IGMP_SNOOPING` (depending on `BRIDGE` and `INET`), and of course, for some neigh functions `CONFIG_INET`.



16/2 2026/09/20 21:30 tool grepper
0m Args:
{
  "Expression": "config VLAN_8021Q",
  "PathPrefix": "net"
}

Results:
{
  "Output": "net/8021q/Kconfig-5-\nnet/8021q/Kconfig:6:config VLAN_8021Q\nnet/8021q/Kconfig-7-\ttristate \"802.1Q/802.1ad VLAN Support\"\n--\nnet/8021q/Kconfig-21-\nnet/8021q/Kconfig:22:config VLAN_8021Q_GVRP\nnet/8021q/Kconfig-23-\tbool \"GVRP (GARP VLAN Registration Protocol) support\"\n--\nnet/8021q/Kconfig-31-\nnet/8021q/Kconfig:32:config VLAN_8021Q_MVRP\nnet/8021q/Kconfig-33-\tbool \"MVRP (Multiple VLAN Registration Protocol) support\"\n"
}

17/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30932
							output: 32
							thoughts: 16
						

Thoughts:
Here's my thought process, as it went:

**Examining the Bridge Configuration**

Okay, so I need to figure out how the `BRIDGE` functionality is configured within this kernel. Let's start by digging into `net/bridge/Kconfig`. That file is usually the central configuration point for anything bridge-related. I expect to find the relevant Kconfig options and their dependencies defined there. It should be pretty straightforward, and will give me a good lay of the land to understand how `BRIDGE` is enabled, disabled, and what other options might influence its availability. From here I can start to form an idea of where to look next based on the configuration logic contained within this file.



18/2 2026/09/20 21:30 tool read-file
0m Args:
{
  "File": "net/bridge/Kconfig",
  "FirstLine": 1,
  "LineCount": 45
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: GPL-2.0-only\n   2:\t#\n   3:\t# 802.1d Ethernet Bridging\n   4:\t#\n   5:\t\n   6:\tconfig BRIDGE\n   7:\t\ttristate \"802.1d Ethernet Bridging\"\n   8:\t\tselect LLC\n   9:\t\tselect STP\n  10:\t\thelp\n  11:\t\t  If you say Y here, then your Linux box will be able to act as an\n  12:\t\t  Ethernet bridge, which means that the different Ethernet segments it\n  13:\t\t  is connected to will appear as one Ethernet to the participants.\n  14:\t\t  Several such bridges can work together to create even larger\n  15:\t\t  networks of Ethernets using the IEEE 802.1 spanning tree algorithm.\n  16:\t\t  As this is a standard, Linux bridges will cooperate properly with\n  17:\t\t  other third party bridge products.\n  18:\t\n  19:\t\t  In order to use the Ethernet bridge, you'll need the bridge\n  20:\t\t  configuration tools; see \u003cfile:Documentation/networking/bridge.rst\u003e\n  21:\t\t  for location. Please read the Bridge mini-HOWTO for more\n  22:\t\t  information.\n  23:\t\n  24:\t\t  If you enable iptables support along with the bridge support then you\n  25:\t\t  turn your bridge into a bridging IP firewall.\n  26:\t\t  iptables will then see the IP packets being bridged, so you need to\n  27:\t\t  take this into account when setting up your firewall rules.\n  28:\t\t  Enabling arptables support when bridging will let arptables see\n  29:\t\t  bridged ARP traffic in the arptables FORWARD chain.\n  30:\t\n  31:\t\t  To compile this code as a module, choose M here: the module\n  32:\t\t  will be called bridge.\n  33:\t\n  34:\t\t  If unsure, say N.\n  35:\t\n  36:\tconfig BRIDGE_IGMP_SNOOPING\n  37:\t\tbool \"IGMP/MLD snooping\"\n  38:\t\tdepends on BRIDGE\n  39:\t\tdepends on INET\n  40:\t\tdefault y\n  41:\t\thelp\n  42:\t\t  If you say Y here, then the Ethernet bridge will be able selectively\n  43:\t\t  forward multicast traffic based on IGMP/MLD traffic received from\n  44:\t\t  each port.\n  45:\t\n"
}

19/2 2026/09/20 21:30 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31761
							output: 296
							thoughts: 1168
						

Thoughts:
**Analysis of Bridge VLAN Flooding Optimization for Fuzzing**

Okay, so I've been digging into this patch. I see it's touching several core bridge components: `BRIDGE`, `BRIDGE_VLAN_FILTERING`, `VLAN_8021Q`, `BRIDGE_IGMP_SNOOPING`, and `INET`. I've confirmed that these are the necessary configurations to enable the relevant code paths. The files impacted are pretty clear: `br_arp_nd_proxy.c`, `br_device.c`, `br_forward.c`, `br_input.c`, `br_mst.c`, `br_private.h`, `br_vlan.c`, and `br_vlan_options.c`.

Now, the interesting part is the functions being modified. I've reviewed them: `br_flood`, `br_flood_vlan`, `br_vlan_rebuild_port_array`, `br_handle_vlan`, `br_is_neigh_suppress_enabled`, `br_is_neigh_forward_grat_enabled`, `br_mst_set_state`, `br_vlan_process_options`, `nbp_vlan_add`, `nbp_vlan_delete`, and `br_multicast_flood`. These are all very specific to VLAN filtering, forwarding, and flooding within an Ethernet bridge. None of them are generic or ubiquitous.

I've checked if `br_flood_vlan` and `br_vlan_rebuild_port_array` are static, and they are, which means they are potential symbols in syzkaller. However, `br_flood` is non-static and calls `br_flood_vlan`, and `nbp_vlan_add` and `nbp_vlan_delete` call `br_vlan_rebuild_port_array`. To be thorough, I'm going to include both the static functions *and* the non-static callers as targets.

The reasoning here is sound: this patch optimizes bridge VLAN flooding. It does this by introducing an RCU-protected port array and list. It also passes VLAN context directly down the flooding path. Neighbor suppression and PVID tracking are adjusted. This warrants fuzzing because the patch touches reachable core networking data structures and forwarding paths. There is dynamic allocation, RCU reclamation, refcounting, and pointer updates during VLAN addition/removal, so there's potential for race conditions, memory issues, and assertions that need to be checked. This all looks solid. I am ready to call `set-results`.



20/2 2026/09/20 21:30 tool set-results
0m Args:
{
  "EnableConfigs": [
    "BRIDGE",
    "BRIDGE_VLAN_FILTERING",
    "VLAN_8021Q",
    "BRIDGE_IGMP_SNOOPING",
    "INET"
  ],
  "FocusSymbols": [
    "br_flood",
    "br_flood_vlan",
    "br_vlan_rebuild_port_array",
    "br_handle_vlan",
    "br_is_neigh_suppress_enabled",
    "br_is_neigh_forward_grat_enabled",
    "br_mst_set_state",
    "nbp_vlan_add",
    "nbp_vlan_delete",
    "br_multicast_flood"
  ],
  "Reasoning": "The patch modifies the Linux Ethernet Bridge subsystem by introducing an RCU-protected port array and list on master VLAN entries to optimize VLAN-aware packet flooding (br_flood, br_flood_vlan). It restructures bridge forwarding paths, changes how PVID and neighbor suppression flags are queried, introduces dynamic memory allocation (kvmalloc/kvfree_rcu), and adds WARN_ON assertions. Because this modifies reachable core networking code accessible via standard virtual network interfaces and netlink commands, fuzzing is warranted to detect potential memory corruption, RCU use-after-free, or invariant violations.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "BRIDGE",
    "BRIDGE_VLAN_FILTERING",
    "VLAN_8021Q",
    "BRIDGE_IGMP_SNOOPING",
    "INET"
  ],
  "FocusSymbols": [
    "br_flood",
    "br_flood_vlan",
    "br_vlan_rebuild_port_array",
    "br_handle_vlan",
    "br_is_neigh_suppress_enabled",
    "br_is_neigh_forward_grat_enabled",
    "br_mst_set_state",
    "nbp_vlan_add",
    "nbp_vlan_delete",
    "br_multicast_flood"
  ],
  "Reasoning": "The patch modifies the Linux Ethernet Bridge subsystem by introducing an RCU-protected port array and list on master VLAN entries to optimize VLAN-aware packet flooding (br_flood, br_flood_vlan). It restructures bridge forwarding paths, changes how PVID and neighbor suppression flags are queried, introduces dynamic memory allocation (kvmalloc/kvfree_rcu), and adds WARN_ON assertions. Because this modifies reachable core networking code accessible via standard virtual network interfaces and netlink commands, fuzzing is warranted to detect potential memory corruption, RCU use-after-free, or invariant violations.",
  "WorthFuzzing": true
}

21/1 2026/09/20 21:30 action If
1m Args:
{
  "WorthFuzzing": true
}

Results:
null

22/2 2026/09/20 21:30 agent kmsan-evaluator
1m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch refactors and optimizes packet flooding and forwarding within the Linux bridge VLAN subsystem:\n1. Replaces the scalar `pvid` and `pvid_state` in `struct net_bridge_vlan_group` with an RCU-protected pointer to `struct net_bridge_vlan`.\n2. Introduces `struct net_bridge_vlan_port_array` dynamically allocated on the master VLAN entry to optimize flooding across VLAN member ports.\n3. Groups the destination port and VLAN into an internal stack structure `struct br_fwd_dst` to pass through `br_flood_*` and `__br_forward`.\n4. Updates neighbor suppression functions to accept `struct net_bridge_vlan *` directly.\n\nAssessment against KMSAN vs KASAN:\n- No uninitialized memory usage: All instances of `struct br_fwd_dst` on the stack are explicitly initialized (using designated/zero initializers or direct assignment to all members) before being passed or read.\n- Dynamic allocations: Bridge VLAN objects continue to be allocated via `kzalloc_obj`, and `net_bridge_vlan_port_array` has its `count` and `vlans` array strictly initialized under RTNL lock based on the count of elements in `masterv-\u003eport_vlist`.\n- No user-space info-leaks: The changes do not expose any uninitialized data, padding, or buffers to user space. `br_vlan_get_info` and `br_vlan_notify` continue to populate the same netlink attributes with properly checked values.\n- Any bugs that could arise here (e.g. RCU lifetime/dereferencing, pointer validity, array index bounds) are covered by KASAN and standard bug detectors.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit d167430449dc7374d66f5cff67b852f548e6a02b
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Sep 20 21:30:13 2026 +0000

    syz-cluster: applied patch under review

diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index 9ce8f440e38fa..da15f4d7c1aef 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -40,6 +40,23 @@ void br_recalculate_neigh_suppress_enabled(struct net_bridge *br)
 }
 
 #if IS_ENABLED(CONFIG_INET)
+static bool
+br_is_neigh_suppress_enabled_vid(const struct net_bridge_port *p, u16 vid)
+{
+	const struct net_bridge_vlan *v = NULL;
+
+	if (p && vid && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags)) {
+		struct net_bridge_vlan_group *vg;
+
+		vg = nbp_vlan_group_rcu(p);
+		v = br_vlan_find(vg, vid);
+		if (!v)
+			return false;
+	}
+
+	return br_is_neigh_suppress_enabled(p, v);
+}
+
 static void br_arp_send(struct net_bridge *br, struct net_bridge_port *p,
 			struct net_device *dev, __be32 dest_ip, __be32 src_ip,
 			const unsigned char *dest_hw,
@@ -159,7 +176,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 		return;
 
 	if (br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) {
-		if (br_is_neigh_suppress_enabled(p, vid))
+		if (br_is_neigh_suppress_enabled_vid(p, vid))
 			return;
 		if (is_unicast_ether_addr(eth_hdr(skb)->h_dest) &&
 		    parp->ar_op == htons(ARPOP_REQUEST))
@@ -211,7 +228,7 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 
 			if ((p && test_bit(BR_PROXYARP_BIT, &p->flags)) ||
 			    (dst && test_bit(BR_PROXYARP_WIFI_BIT, &dst->flags)) ||
-			    br_is_neigh_suppress_enabled(dst, vid)) {
+			    br_is_neigh_suppress_enabled_vid(dst, vid)) {
 				if (!vid)
 					br_arp_send(br, p, skb->dev, sip, tip,
 						    sha, ha, sha, 0, 0);
@@ -424,7 +441,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 	BR_INPUT_SKB_CB(skb)->proxyarp_replied = 0;
 	BR_INPUT_SKB_CB(skb)->grat_arp = 0;
 
-	if (br_is_neigh_suppress_enabled(p, vid))
+	if (br_is_neigh_suppress_enabled_vid(p, vid))
 		return;
 
 	if (is_unicast_ether_addr(eth_hdr(skb)->h_dest) &&
@@ -486,7 +503,7 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 			const struct net_bridge_port *dst = READ_ONCE(f->dst);
 			bool replied = false;
 
-			if (br_is_neigh_suppress_enabled(dst, vid)) {
+			if (br_is_neigh_suppress_enabled_vid(dst, vid)) {
 				if (vid != 0)
 					br_nd_send(br, p, skb, n, ha,
 						   skb->vlan_proto,
@@ -509,35 +526,25 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 }
 #endif
 
-bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid)
+bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p,
+				  const struct net_bridge_vlan *v)
 {
 	if (!p)
 		return false;
 
-	if (vid && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags)) {
-		struct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p);
-		struct net_bridge_vlan *v;
-
-		v = br_vlan_find(vg, vid);
-		if (!v)
-			return false;
+	if (v && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags))
 		return !!(READ_ONCE(v->priv_flags) &
 			  BR_VLFLAG_NEIGH_SUPPRESS_ENABLED);
-	}
+
 	return test_bit(BR_NEIGH_SUPPRESS_BIT, &p->flags);
 }
 
-bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid)
+bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p,
+				      const struct net_bridge_vlan *v)
 {
-	if (vid && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags)) {
-		struct net_bridge_vlan_group *vg = nbp_vlan_group_rcu(p);
-		struct net_bridge_vlan *v;
-
-		v = br_vlan_find(vg, vid);
-		if (!v)
-			return false;
+	if (v && test_bit(BR_NEIGH_VLAN_SUPPRESS_BIT, &p->flags))
 		return !!(READ_ONCE(v->priv_flags) &
 			  BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED);
-	}
+
 	return test_bit(BR_NEIGH_FORWARD_GRAT_BIT, &p->flags);
 }
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index e01c44a90d84b..ce9ea9ac3d0ad 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -89,10 +89,10 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 
 	dest = eth_hdr(skb)->h_dest;
 	if (is_broadcast_ether_addr(dest)) {
-		br_flood(br, skb, BR_PKT_BROADCAST, false, true, vid);
+		br_flood(br, vlan, skb, BR_PKT_BROADCAST, false, true);
 	} else if (is_multicast_ether_addr(dest)) {
 		if (unlikely(netpoll_tx_running(dev))) {
-			br_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);
+			br_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);
 			goto out;
 		}
 		if (br_multicast_rcv(&brmctx, &pmctx_null, vlan, skb, vid)) {
@@ -105,11 +105,11 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 		    br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst))
 			br_multicast_flood(mdst, skb, brmctx, false, true);
 		else
-			br_flood(br, skb, BR_PKT_MULTICAST, false, true, vid);
+			br_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);
 	} else if ((dst = br_fdb_find_rcu(br, dest, vid)) != NULL) {
 		br_forward(READ_ONCE(dst->dst), skb, false, true);
 	} else {
-		br_flood(br, skb, BR_PKT_UNICAST, false, true, vid);
+		br_flood(br, vlan, skb, BR_PKT_UNICAST, false, true);
 	}
 out:
 	rcu_read_unlock();
diff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c
index 46c762ca5177e..b5eece1ff9e8c 100644
--- a/net/bridge/br_forward.c
+++ b/net/bridge/br_forward.c
@@ -7,7 +7,6 @@
  *	Lennert Buytenhek		<buytenh@gnu.org>
  */
 
-#include <linux/err.h>
 #include <linux/slab.h>
 #include <linux/kernel.h>
 #include <linux/netdevice.h>
@@ -17,16 +16,31 @@
 #include <linux/netfilter_bridge.h>
 #include "br_private.h"
 
+struct br_fwd_dst {
+	const struct net_bridge_port *port;
+	struct net_bridge_vlan *vlan;
+};
+
+static bool should_deliver_vlan(const struct br_fwd_dst *fwd,
+				const struct sk_buff *skb)
+{
+	if (fwd->vlan)
+		return br_vlan_state_allowed(br_vlan_get_state(fwd->vlan),
+					     false);
+
+	return br_allowed_egress(nbp_vlan_group_rcu(fwd->port), skb);
+}
+
 /* Don't forward packets to originating port or forwarding disabled */
-static inline int should_deliver(const struct net_bridge_port *p,
-				 const struct sk_buff *skb)
+static __always_inline bool should_deliver(const struct br_fwd_dst *fwd,
+					   const struct sk_buff *skb)
 {
-	struct net_bridge_vlan_group *vg;
+	const struct net_bridge_port *p = fwd->port;
 
-	vg = nbp_vlan_group_rcu(p);
 	return (test_bit(BR_HAIRPIN_MODE_BIT, &p->flags) || skb->dev != p->dev) &&
 		(br_mst_is_enabled(p) || p->state == BR_STATE_FORWARDING) &&
-		br_allowed_egress(vg, skb) && nbp_switchdev_allowed_egress(p, skb) &&
+		should_deliver_vlan(fwd, skb) &&
+		nbp_switchdev_allowed_egress(p, skb) &&
 		!br_skb_isolated(p, skb);
 }
 
@@ -70,9 +84,10 @@ int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
 }
 EXPORT_SYMBOL_GPL(br_forward_finish);
 
-static void __br_forward(const struct net_bridge_port *to,
+static void __br_forward(const struct br_fwd_dst *fwd,
 			 struct sk_buff *skb, bool local_orig)
 {
+	const struct net_bridge_port *to = fwd->port;
 	struct net_bridge_vlan_group *vg;
 	struct net_device *indev;
 	struct net *net;
@@ -83,8 +98,8 @@ static void __br_forward(const struct net_bridge_port *to,
 	 */
 	nbp_switchdev_frame_mark_tx_fwd_offload(to, skb);
 
-	vg = nbp_vlan_group_rcu(to);
-	skb = br_handle_vlan(to->br, to, vg, skb);
+	vg = fwd->vlan ? NULL : nbp_vlan_group_rcu(to);
+	skb = br_handle_vlan(to->br, to, vg, fwd->vlan, skb);
 	if (!skb)
 		return;
 
@@ -117,7 +132,7 @@ static void __br_forward(const struct net_bridge_port *to,
 		br_forward_finish);
 }
 
-static int deliver_clone(const struct net_bridge_port *prev,
+static int deliver_clone(const struct br_fwd_dst *fwd,
 			 struct sk_buff *skb, bool local_orig)
 {
 	struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
@@ -128,7 +143,7 @@ static int deliver_clone(const struct net_bridge_port *prev,
 		return -ENOMEM;
 	}
 
-	__br_forward(prev, skb, local_orig);
+	__br_forward(fwd, skb, local_orig);
 	return 0;
 }
 
@@ -144,6 +159,8 @@ static int deliver_clone(const struct net_bridge_port *prev,
 void br_forward(const struct net_bridge_port *to,
 		struct sk_buff *skb, bool local_rcv, bool local_orig)
 {
+	struct br_fwd_dst fwd;
+
 	if (unlikely(!to))
 		goto out;
 
@@ -159,11 +176,13 @@ void br_forward(const struct net_bridge_port *to,
 		to = backup_port;
 	}
 
-	if (should_deliver(to, skb)) {
+	fwd.port = to;
+	fwd.vlan = NULL;
+	if (should_deliver(&fwd, skb)) {
 		if (local_rcv)
-			deliver_clone(to, skb, local_orig);
+			deliver_clone(&fwd, skb, local_orig);
 		else
-			__br_forward(to, skb, local_orig);
+			__br_forward(&fwd, skb, local_orig);
 		return;
 	}
 
@@ -173,95 +192,162 @@ void br_forward(const struct net_bridge_port *to,
 }
 EXPORT_SYMBOL_GPL(br_forward);
 
-static struct net_bridge_port *maybe_deliver(
-	struct net_bridge_port *prev, struct net_bridge_port *p,
-	struct sk_buff *skb, bool local_orig)
+static int maybe_deliver(struct br_fwd_dst *prev, const struct br_fwd_dst *fwd,
+			 struct sk_buff *skb, bool local_orig)
 {
+	const struct net_bridge_port *p = fwd->port;
 	u8 igmp_type = br_multicast_igmp_type(skb);
 	int err;
 
-	if (!should_deliver(p, skb))
-		return prev;
+	if (!should_deliver(fwd, skb))
+		return 0;
 
 	nbp_switchdev_frame_mark_tx_fwd_to_hwdom(p, skb);
 
-	if (!prev)
+	if (!prev->port)
 		goto out;
 
 	err = deliver_clone(prev, skb, local_orig);
 	if (err)
-		return ERR_PTR(err);
+		return err;
 out:
 	br_multicast_count(p->br, p, skb, igmp_type, BR_MCAST_DIR_TX);
+	*prev = *fwd;
 
-	return p;
+	return 0;
 }
 
-/* called under rcu_read_lock */
-void br_flood(struct net_bridge *br, struct sk_buff *skb,
-	      enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,
-	      u16 vid)
+static void br_flood_finish(const struct br_fwd_dst *fwd, int err,
+			    struct sk_buff *skb,
+			    bool local_rcv, bool local_orig)
 {
 	enum skb_drop_reason reason = SKB_DROP_REASON_NO_TX_TARGET;
-	struct net_bridge_port *prev = NULL;
-	struct net_bridge_port *p;
 
-	br_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);
+	if (err || !fwd->port) {
+		if (err)
+			reason = err == -ENOMEM ? SKB_DROP_REASON_NOMEM :
+						  SKB_DROP_REASON_NOT_SPECIFIED;
+
+		if (!local_rcv)
+			kfree_skb_reason(skb, reason);
+		return;
+	}
 
-	list_for_each_entry_rcu(p, &br->port_list, list) {
-		/* Do not flood unicast traffic to ports that turn it off, nor
-		 * other traffic if flood off, except for traffic we originate
+	if (local_rcv)
+		deliver_clone(fwd, skb, local_orig);
+	else
+		__br_forward(fwd, skb, local_orig);
+}
+
+static int br_flood_port(struct br_fwd_dst *prev,
+			 const struct br_fwd_dst *fwd, struct sk_buff *skb,
+			 enum br_pkt_type pkt_type, bool local_orig)
+{
+	const struct net_bridge_port *p = fwd->port;
+
+	/* Do not flood unicast traffic to ports that turn it off, nor
+	 * other traffic if flood off, except for traffic we originate
+	 */
+	switch (pkt_type) {
+	case BR_PKT_UNICAST:
+		if (!test_bit(BR_FLOOD_BIT, &p->flags))
+			return 0;
+		break;
+	case BR_PKT_MULTICAST:
+		if (!test_bit(BR_MCAST_FLOOD_BIT, &p->flags) &&
+		    skb->dev != p->br->dev)
+			return 0;
+		break;
+	case BR_PKT_BROADCAST:
+		if (!test_bit(BR_BCAST_FLOOD_BIT, &p->flags) &&
+		    skb->dev != p->br->dev)
+			return 0;
+		break;
+	}
+
+	/* Do not flood to ports that enable proxy ARP */
+	if (test_bit(BR_PROXYARP_BIT, &p->flags))
+		return 0;
+	if (BR_INPUT_SKB_CB(skb)->proxyarp_replied) {
+		if (test_bit(BR_PROXYARP_WIFI_BIT, &p->flags))
+			return 0;
+		/* For gratuitous ARPs/NAs, check neigh_forward_grat.
+		 * For regular ARPs/NDs, check only neigh_suppress.
 		 */
-		switch (pkt_type) {
-		case BR_PKT_UNICAST:
-			if (!test_bit(BR_FLOOD_BIT, &p->flags))
-				continue;
-			break;
-		case BR_PKT_MULTICAST:
-			if (!test_bit(BR_MCAST_FLOOD_BIT, &p->flags) && skb->dev != br->dev)
-				continue;
-			break;
-		case BR_PKT_BROADCAST:
-			if (!test_bit(BR_BCAST_FLOOD_BIT, &p->flags) && skb->dev != br->dev)
-				continue;
-			break;
-		}
+		if (br_is_neigh_suppress_enabled(p, fwd->vlan) &&
+		    (!BR_INPUT_SKB_CB(skb)->grat_arp ||
+		     !br_is_neigh_forward_grat_enabled(p, fwd->vlan)))
+			return 0;
+	}
 
-		/* Do not flood to ports that enable proxy ARP */
-		if (test_bit(BR_PROXYARP_BIT, &p->flags))
-			continue;
-		if (BR_INPUT_SKB_CB(skb)->proxyarp_replied) {
-			if (test_bit(BR_PROXYARP_WIFI_BIT, &p->flags))
-				continue;
-			/* For gratuitous ARPs/NAs, check neigh_forward_grat.
-			 * For regular ARPs/NDs, check only neigh_suppress.
-			 */
-			if (br_is_neigh_suppress_enabled(p, vid) &&
-			    (!BR_INPUT_SKB_CB(skb)->grat_arp ||
-			     !br_is_neigh_forward_grat_enabled(p, vid)))
-				continue;
-		}
+	return maybe_deliver(prev, fwd, skb, local_orig);
+}
 
-		prev = maybe_deliver(prev, p, skb, local_orig);
-		if (IS_ERR(prev)) {
-			reason = PTR_ERR(prev) == -ENOMEM ? SKB_DROP_REASON_NOMEM :
-				 SKB_DROP_REASON_NOT_SPECIFIED;
-			goto out;
+static int br_flood_vlan(struct br_fwd_dst *prev,
+			 struct net_bridge_vlan *v, struct sk_buff *skb,
+			 enum br_pkt_type pkt_type, bool local_orig)
+{
+	struct net_bridge_vlan_port_array *array;
+	struct net_bridge_vlan *masterv, *pv;
+	struct br_fwd_dst dst;
+	int err;
+
+	masterv = br_vlan_is_master(v) ? v : v->brvlan;
+	array = rcu_dereference(masterv->port_array);
+	if (array) {
+		unsigned int i;
+
+		for (i = 0; i < array->count; i++) {
+			pv = array->vlans[i];
+			dst.port = pv->port;
+			dst.vlan = pv;
+			err = br_flood_port(prev, &dst, skb, pkt_type,
+					    local_orig);
+			if (err)
+				return err;
+		}
+	} else {
+		list_for_each_entry_rcu(pv, &masterv->port_vlist, port_vlist) {
+			dst.port = pv->port;
+			dst.vlan = pv;
+			err = br_flood_port(prev, &dst, skb, pkt_type,
+					    local_orig);
+			if (err)
+				return err;
 		}
 	}
 
-	if (!prev)
-		goto out;
+	return 0;
+}
 
-	if (local_rcv)
-		deliver_clone(prev, skb, local_orig);
-	else
-		__br_forward(prev, skb, local_orig);
-	return;
+/* called under rcu_read_lock */
+void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,
+	      struct sk_buff *skb, enum br_pkt_type pkt_type,
+	      bool local_rcv, bool local_orig)
+{
+	struct br_fwd_dst prev = {};
+	int err = 0;
 
-out:
-	if (!local_rcv)
-		kfree_skb_reason(skb, reason);
+	br_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);
+
+	if (v) {
+		err = br_flood_vlan(&prev, v, skb, pkt_type, local_orig);
+	} else {
+		struct net_bridge_port *p;
+
+		list_for_each_entry_rcu(p, &br->port_list, list) {
+			struct br_fwd_dst fwd = {
+				.port = p,
+			};
+
+			err = br_flood_port(&prev, &fwd, skb, pkt_type,
+					    local_orig);
+			if (err)
+				break;
+		}
+	}
+
+	br_flood_finish(&prev, err, skb, local_rcv, local_orig);
 }
 
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
@@ -270,9 +356,12 @@ static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,
 {
 	struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
 	const unsigned char *src = eth_hdr(skb)->h_source;
+	struct br_fwd_dst fwd = {
+		.port = p,
+	};
 	struct sk_buff *nskb;
 
-	if (!should_deliver(p, skb))
+	if (!should_deliver(&fwd, skb))
 		return;
 
 	/* Even with hairpin, no soliloquies - prevent breaking IPv6 DAD */
@@ -292,7 +381,7 @@ static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,
 	if (!is_broadcast_ether_addr(addr))
 		memcpy(eth_hdr(skb)->h_dest, addr, ETH_ALEN);
 
-	__br_forward(p, skb, local_orig);
+	__br_forward(&fwd, skb, local_orig);
 }
 
 /* called with rcu_read_lock */
@@ -301,11 +390,11 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 			struct net_bridge_mcast *brmctx,
 			bool local_rcv, bool local_orig)
 {
-	enum skb_drop_reason reason = SKB_DROP_REASON_NO_TX_TARGET;
-	struct net_bridge_port *prev = NULL;
 	struct net_bridge_port_group *p;
 	bool allow_mode_include = true;
+	struct br_fwd_dst prev = {};
 	struct hlist_node *rp;
+	int err = 0;
 
 	rp = br_multicast_get_first_rport_node(brmctx, skb);
 
@@ -321,6 +410,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 
 	while (p || rp) {
 		struct net_bridge_port *port, *lport, *rport;
+		struct br_fwd_dst fwd = {};
 
 		lport = p ? p->key.port : NULL;
 		rport = br_multicast_rport_from_node_skb(rp, skb);
@@ -342,12 +432,11 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 			port = rport;
 		}
 
-		prev = maybe_deliver(prev, port, skb, local_orig);
-		if (IS_ERR(prev)) {
-			reason = PTR_ERR(prev) == -ENOMEM ? SKB_DROP_REASON_NOMEM :
-				 SKB_DROP_REASON_NOT_SPECIFIED;
-			goto out;
-		}
+		fwd.port = port;
+		err = maybe_deliver(&prev, &fwd, skb, local_orig);
+		if (err)
+			break;
+
 delivered:
 		if ((unsigned long)lport >= (unsigned long)port)
 			p = rcu_dereference(p->next);
@@ -355,17 +444,6 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 			rp = rcu_dereference(hlist_next_rcu(rp));
 	}
 
-	if (!prev)
-		goto out;
-
-	if (local_rcv)
-		deliver_clone(prev, skb, local_orig);
-	else
-		__br_forward(prev, skb, local_orig);
-	return;
-
-out:
-	if (!local_rcv)
-		kfree_skb_reason(skb, reason);
+	br_flood_finish(&prev, err, skb, local_rcv, local_orig);
 }
 #endif
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 8bed72baf1615..4357d78524a68 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -58,7 +58,7 @@ static int br_pass_frame_up(struct sk_buff *skb, bool promisc)
 
 	indev = skb->dev;
 	skb->dev = brdev;
-	skb = br_handle_vlan(br, NULL, vg, skb);
+	skb = br_handle_vlan(br, NULL, vg, NULL, skb);
 	if (!skb)
 		return NET_RX_DROP;
 	/* update the multicast stats if the packet is IGMP/MLD */
@@ -226,7 +226,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 		br_forward(READ_ONCE(dst->dst), skb, local_rcv, false);
 	} else {
 		if (!mcast_hit)
-			br_flood(br, skb, pkt_type, local_rcv, false, vid);
+			br_flood(br, vlan, skb, pkt_type, local_rcv, false);
 		else
 			br_multicast_flood(mdst, skb, brmctx, local_rcv, false);
 	}
diff --git a/net/bridge/br_mst.c b/net/bridge/br_mst.c
index 63fc1dd0e9bc0..252d5c927f716 100644
--- a/net/bridge/br_mst.c
+++ b/net/bridge/br_mst.c
@@ -79,16 +79,11 @@ int br_mst_get_state(const struct net_device *dev, u16 msti, u8 *state)
 }
 EXPORT_SYMBOL_GPL(br_mst_get_state);
 
-static void br_mst_vlan_set_state(struct net_bridge_vlan_group *vg,
-				  struct net_bridge_vlan *v,
-				  u8 state)
+static void br_mst_vlan_set_state(struct net_bridge_vlan *v, u8 state)
 {
 	if (br_vlan_get_state(v) == state)
 		return;
 
-	if (v->vid == br_get_pvid(vg))
-		br_vlan_set_pvid_state(vg, state);
-
 	br_vlan_set_state(v, state);
 }
 
@@ -129,7 +124,7 @@ int br_mst_set_state(struct net_bridge_port *p, u16 msti, u8 state,
 		if (READ_ONCE(v->brvlan->msti) != msti)
 			continue;
 
-		br_mst_vlan_set_state(vg, v, state);
+		br_mst_vlan_set_state(v, state);
 	}
 
 out_rcu_unlock:
@@ -149,13 +144,13 @@ static void br_mst_vlan_sync_state(struct net_bridge_vlan *pv, u16 msti)
 		 * it.
 		 */
 		if (v != pv && v->brvlan->msti == msti) {
-			br_mst_vlan_set_state(vg, pv, br_vlan_get_state(v));
+			br_mst_vlan_set_state(pv, br_vlan_get_state(v));
 			return;
 		}
 	}
 
 	/* Otherwise, start out in a new MSTI with all ports disabled. */
-	return br_mst_vlan_set_state(vg, pv, BR_STATE_DISABLED);
+	return br_mst_vlan_set_state(pv, BR_STATE_DISABLED);
 }
 
 int br_mst_vlan_set_msti(struct net_bridge_vlan *mv, u16 msti)
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 09c397e303307..67117fb3dc880 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -190,6 +190,17 @@ enum {
 	BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED = BIT(6),
 };
 
+/* start publishing arrays when there're > BR_VLAN_PORT_ARRAY_THRESHOLD
+ * port-VLANs
+ */
+#define BR_VLAN_PORT_ARRAY_THRESHOLD 8
+
+struct net_bridge_vlan_port_array {
+	struct rcu_head		rcu;
+	unsigned int		count;
+	struct net_bridge_vlan	*vlans[];
+};
+
 /**
  * struct net_bridge_vlan - per-vlan entry
  *
@@ -210,6 +221,8 @@ enum {
  * @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast
  *                  context
  * @msti: if MASTER flag set, this holds the VLANs MST instance
+ * @port_array: if MASTER flag set, this is the port-VLAN array
+ * @port_vlist: if MASTER flag set, this is the port-VLAN list
  * @vlist: sorted list of VLAN entries
  * @rcu: used for entry destruction
  *
@@ -244,6 +257,8 @@ struct net_bridge_vlan {
 
 	u16				msti;
 
+	struct net_bridge_vlan_port_array __rcu *port_array;
+	struct list_head		port_vlist;
 	struct list_head		vlist;
 
 	struct rcu_head			rcu;
@@ -256,8 +271,7 @@ struct net_bridge_vlan {
  * @tunnel_hash: Hash table to map from tunnel key ID (e.g. VXLAN VNI) to VLAN
  * @vlan_list: sorted VLAN entry list
  * @num_vlans: number of total VLAN entries
- * @pvid: PVID VLAN id
- * @pvid_state: PVID's STP state (e.g. forwarding, learning, blocking)
+ * @pvid: RCU-protected PVID VLAN entry
  *
  * IMPORTANT: Be careful when checking if there're VLAN entries using list
  *            primitives because the bridge can have entries in its list which
@@ -269,9 +283,8 @@ struct net_bridge_vlan_group {
 	struct rhashtable		vlan_hash;
 	struct rhashtable		tunnel_hash;
 	struct list_head		vlan_list;
+	struct net_bridge_vlan		__rcu *pvid;
 	u16				num_vlans;
-	u16				pvid;
-	u8				pvid_state;
 };
 
 /* bridge fdb flags */
@@ -912,9 +925,9 @@ int br_dev_queue_push_xmit(struct net *net, struct sock *sk, struct sk_buff *skb
 void br_forward(const struct net_bridge_port *to, struct sk_buff *skb,
 		bool local_rcv, bool local_orig);
 int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
-void br_flood(struct net_bridge *br, struct sk_buff *skb,
-	      enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,
-	      u16 vid);
+void br_flood(struct net_bridge *br, struct net_bridge_vlan *v,
+	      struct sk_buff *skb, enum br_pkt_type pkt_type,
+	      bool local_rcv, bool local_orig);
 
 /* return true if both source port and dest port are isolated */
 static inline bool br_skb_isolated(const struct net_bridge_port *to,
@@ -1593,6 +1606,7 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid);
 struct sk_buff *br_handle_vlan(struct net_bridge *br,
 			       const struct net_bridge_port *port,
 			       struct net_bridge_vlan_group *vg,
+			       struct net_bridge_vlan *vlan,
 			       struct sk_buff *skb);
 int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags,
 		bool *changed, struct netlink_ext_ack *extack);
@@ -1687,10 +1701,16 @@ static inline int br_vlan_get_tag(const struct sk_buff *skb, u16 *vid)
 
 static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)
 {
+	struct net_bridge_vlan *pvid;
+
 	if (!vg)
 		return 0;
 
-	return READ_ONCE(vg->pvid);
+	pvid = rcu_dereference_rtnl(vg->pvid);
+	if (!pvid)
+		return 0;
+
+	return pvid->vid;
 }
 
 static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
@@ -1724,6 +1744,7 @@ static inline bool br_should_learn(struct net_bridge_port *p,
 static inline struct sk_buff *br_handle_vlan(struct net_bridge *br,
 					     const struct net_bridge_port *port,
 					     struct net_bridge_vlan_group *vg,
+					     struct net_bridge_vlan *vlan,
 					     struct sk_buff *skb)
 {
 	return skb;
@@ -1922,17 +1943,6 @@ static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
 	br_multicast_update_vlan_mcast_ctx(v, state);
 }
 
-static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
-{
-	return READ_ONCE(vg->pvid_state);
-}
-
-static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,
-					  u8 state)
-{
-	WRITE_ONCE(vg->pvid_state, state);
-}
-
 /* learn_allow is true at ingress and false at egress */
 static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
 {
@@ -1945,6 +1955,11 @@ static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
 		return false;
 	}
 }
+#else
+static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
+{
+	return false;
+}
 #endif
 
 /* br_mst.c */
@@ -2375,6 +2390,8 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
 void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
 		       u16 vid, struct net_bridge_port *p, struct nd_msg *msg);
 struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb);
-bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid);
-bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p, u16 vid);
+bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p,
+				  const struct net_bridge_vlan *v);
+bool br_is_neigh_forward_grat_enabled(const struct net_bridge_port *p,
+				      const struct net_bridge_vlan *v);
 #endif
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index 34ede0f3e4523..ce5aa15c45402 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -35,21 +35,21 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
 }
 
 static void __vlan_add_pvid(struct net_bridge_vlan_group *vg,
-			    const struct net_bridge_vlan *v)
+			    struct net_bridge_vlan *v)
 {
-	if (vg->pvid == v->vid)
+	if (rcu_access_pointer(vg->pvid) == v)
 		return;
 
-	br_vlan_set_pvid_state(vg, br_vlan_get_state(v));
-	WRITE_ONCE(vg->pvid, v->vid);
+	RCU_INIT_POINTER(vg->pvid, v);
 }
 
-static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg, u16 vid)
+static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg,
+			       struct net_bridge_vlan *v)
 {
-	if (vg->pvid != vid)
+	if (rcu_access_pointer(vg->pvid) != v)
 		return;
 
-	WRITE_ONCE(vg->pvid, 0);
+	RCU_INIT_POINTER(vg->pvid, NULL);
 }
 
 /* Update the BRIDGE_VLAN_INFO_PVID and BRIDGE_VLAN_INFO_UNTAGGED flags of @v.
@@ -60,6 +60,7 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
 				bool commit)
 {
 	struct net_bridge_vlan_group *vg;
+	struct net_bridge_vlan *pvid;
 	u16 vlan_flags;
 	bool change;
 
@@ -70,7 +71,8 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
 
 	/* check if anything would be changed on commit */
 	vlan_flags = v->flags;
-	change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(vg->pvid != v->vid) ||
+	pvid = rtnl_dereference(vg->pvid);
+	change = !!(flags & BRIDGE_VLAN_INFO_PVID) == !!(pvid != v) ||
 		 ((flags ^ vlan_flags) & BRIDGE_VLAN_INFO_UNTAGGED);
 
 	if (!commit)
@@ -79,7 +81,7 @@ static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,
 	if (flags & BRIDGE_VLAN_INFO_PVID)
 		__vlan_add_pvid(vg, v);
 	else
-		__vlan_delete_pvid(vg, v->vid);
+		__vlan_delete_pvid(vg, v);
 
 	if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
 		vlan_flags |= BRIDGE_VLAN_INFO_UNTAGGED;
@@ -128,10 +130,12 @@ static void __vlan_add_list(struct net_bridge_vlan *v)
 	struct list_head *headp, *hpos;
 	struct net_bridge_vlan *vent;
 
-	if (br_vlan_is_master(v))
+	if (br_vlan_is_master(v)) {
 		vg = br_vlan_group(v->br);
-	else
+	} else {
 		vg = nbp_vlan_group(v->port);
+		list_add_rcu(&v->port_vlist, &v->brvlan->port_vlist);
+	}
 
 	headp = &vg->vlan_list;
 	list_for_each_prev(hpos, headp) {
@@ -145,6 +149,10 @@ static void __vlan_add_list(struct net_bridge_vlan *v)
 static void __vlan_del_list(struct net_bridge_vlan *v)
 {
 	list_del_rcu(&v->vlist);
+	if (br_vlan_is_master(v))
+		WARN_ON(!list_empty(&v->port_vlist));
+	else
+		list_del_rcu(&v->port_vlist);
 }
 
 static int __vlan_vid_del(struct net_device *dev, struct net_bridge *br,
@@ -250,6 +258,36 @@ static void br_vlan_init_state(struct net_bridge_vlan *v)
 	v->msti = 0;
 }
 
+static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)
+{
+	return refcount_read(&masterv->refcnt) - br_vlan_is_brentry(masterv);
+}
+
+static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,
+				       unsigned int count)
+{
+	struct net_bridge_vlan_port_array *array = NULL, *old;
+	unsigned int i = 0;
+
+	WARN_ON(!br_vlan_is_master(masterv));
+
+	if (count > BR_VLAN_PORT_ARRAY_THRESHOLD)
+		array = kvmalloc(struct_size(array, vlans, count), GFP_KERNEL);
+
+	if (array) {
+		struct net_bridge_vlan *pv;
+
+		array->count = count;
+		list_for_each_entry(pv, &masterv->port_vlist, port_vlist)
+			array->vlans[i++] = pv;
+	}
+
+	old = rtnl_dereference(masterv->port_array);
+	rcu_assign_pointer(masterv->port_array, array);
+	if (old)
+		kvfree_rcu(old, rcu);
+}
+
 /* This is the shared VLAN add function which works for both ports and bridge
  * devices. There are four possible calls to this function in terms of the
  * vlan entry type:
@@ -333,6 +371,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
 		}
 		br_multicast_ctx_init(br, v, &v->br_mcast_ctx);
 		v->priv_flags |= BR_VLFLAG_GLOBAL_MCAST_ENABLED;
+		INIT_LIST_HEAD(&v->port_vlist);
 	}
 
 	/* Add the dev mac and count the vlan only if it's usable */
@@ -359,8 +398,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
 	__vlan_flags_commit(v, flags);
 	br_multicast_toggle_one_vlan(v, true);
 
-	if (p)
+	if (p) {
+		br_vlan_rebuild_port_array(masterv, br_vlan_num_ports(masterv));
 		nbp_vlan_set_vlan_dev_state(p, v->vid);
+	}
 out:
 	return err;
 
@@ -403,7 +444,7 @@ static void __vlan_del(struct net_bridge_vlan *v)
 		masterv = v->brvlan;
 	}
 
-	__vlan_delete_pvid(vg, v->vid);
+	__vlan_delete_pvid(vg, v);
 	if (p) {
 		err = __vlan_vid_del(p->dev, p->br, v);
 		if (err)
@@ -429,6 +470,9 @@ static void __vlan_del(struct net_bridge_vlan *v)
 		rhashtable_remove_fast(&vg->vlan_hash, &v->vnode,
 				       br_vlan_rht_params);
 		__vlan_del_list(v);
+		/* -1 because br_vlan_put_master() is called later */
+		br_vlan_rebuild_port_array(masterv,
+					   br_vlan_num_ports(masterv) - 1);
 		nbp_vlan_set_vlan_dev_state(p, v->vid);
 		br_multicast_toggle_one_vlan(v, false);
 		br_multicast_port_ctx_deinit(&v->port_mcast_ctx);
@@ -453,7 +497,7 @@ static void __vlan_flush(const struct net_bridge *br,
 	struct net_bridge_vlan *vlan, *tmp;
 	u16 v_start = 0, v_end = 0;
 
-	__vlan_delete_pvid(vg, vg->pvid);
+	__vlan_delete_pvid(vg, rtnl_dereference(vg->pvid));
 	list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) {
 		/* take care of disjoint ranges */
 		if (!v_start) {
@@ -476,10 +520,10 @@ static void __vlan_flush(const struct net_bridge *br,
 struct sk_buff *br_handle_vlan(struct net_bridge *br,
 			       const struct net_bridge_port *p,
 			       struct net_bridge_vlan_group *vg,
+			       struct net_bridge_vlan *v,
 			       struct sk_buff *skb)
 {
 	struct pcpu_sw_netstats *stats;
-	struct net_bridge_vlan *v;
 	u16 vid;
 
 	/* If this packet was not filtered at input, let it pass */
@@ -490,19 +534,22 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
 	 * a valid vlan id.  If the vlan id has untagged flag set,
 	 * send untagged; otherwise, send tagged.
 	 */
-	br_vlan_get_tag(skb, &vid);
-	v = br_vlan_find(vg, vid);
-	/* Vlan entry must be configured at this point.  The
-	 * only exception is the bridge is set in promisc mode and the
-	 * packet is destined for the bridge device.  In this case
-	 * pass the packet as is.
-	 */
-	if (!v || !br_vlan_should_use(v)) {
-		if ((br->dev->flags & IFF_PROMISC) && skb->dev == br->dev) {
-			goto out;
-		} else {
-			kfree_skb(skb);
-			return NULL;
+	if (!v) {
+		br_vlan_get_tag(skb, &vid);
+		v = br_vlan_find(vg, vid);
+		/* Vlan entry must be configured at this point.  The
+		 * only exception is the bridge is set in promisc mode and the
+		 * packet is destined for the bridge device.  In this case
+		 * pass the packet as is.
+		 */
+		if (!v || !br_vlan_should_use(v)) {
+			if ((br->dev->flags & IFF_PROMISC) &&
+			    skb->dev == br->dev) {
+				goto out;
+			} else {
+				kfree_skb(skb);
+				return NULL;
+			}
 		}
 	}
 	if (br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
@@ -579,42 +626,32 @@ static bool __allowed_ingress(const struct net_bridge *br,
 	}
 
 	if (!*vid) {
-		u16 pvid = br_get_pvid(vg);
-
+		v = vg ? rcu_dereference(vg->pvid) : NULL;
 		/* Frame had a tag with VID 0 or did not have a tag.
 		 * See if pvid is set on this port.  That tells us which
 		 * vlan untagged or priority-tagged traffic belongs to.
 		 */
-		if (!pvid)
+		if (!v)
 			goto drop;
 
 		/* PVID is set on this port.  Any untagged or priority-tagged
 		 * ingress frame is considered to belong to this vlan.
 		 */
-		*vid = pvid;
+		*vid = v->vid;
 		if (likely(!tagged))
 			/* Untagged Frame. */
-			__vlan_hwaccel_put_tag(skb, br->vlan_proto, pvid);
+			__vlan_hwaccel_put_tag(skb, br->vlan_proto, v->vid);
 		else
 			/* Priority-tagged Frame.
 			 * At this point, we know that skb->vlan_tci VID
 			 * field was 0.
 			 * We update only VID field and preserve PCP field.
 			 */
-			skb->vlan_tci |= pvid;
-
-		/* if snooping and stats are disabled we can avoid the lookup */
-		if (!br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
-		    !br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
-			if (*state == BR_STATE_FORWARDING) {
-				*state = br_vlan_get_pvid_state(vg);
-				if (!br_vlan_state_allowed(*state, true))
-					goto drop;
-			}
-			return true;
-		}
+			skb->vlan_tci |= v->vid;
+	} else {
+		v = br_vlan_find(vg, *vid);
 	}
-	v = br_vlan_find(vg, *vid);
+
 	if (!v || !br_vlan_should_use(v))
 		goto drop;
 
@@ -697,11 +734,10 @@ bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)
 		*vid = 0;
 
 	if (!*vid) {
-		*vid = br_get_pvid(vg);
-		if (!*vid ||
-		    !br_vlan_state_allowed(br_vlan_get_pvid_state(vg), true))
+		v = rcu_dereference(vg->pvid);
+		if (!v || !br_vlan_state_allowed(br_vlan_get_state(v), true))
 			return false;
-
+		*vid = v->vid;
 		return true;
 	}
 
@@ -1061,17 +1097,10 @@ int br_vlan_set_stats_per_port(struct net_bridge *br, unsigned long val)
 
 static bool vlan_default_pvid(struct net_bridge_vlan_group *vg, u16 vid)
 {
-	struct net_bridge_vlan *v;
+	struct net_bridge_vlan *pvid = rtnl_dereference(vg->pvid);
 
-	if (vid != vg->pvid)
-		return false;
-
-	v = br_vlan_lookup(&vg->vlan_hash, vid);
-	if (v && br_vlan_should_use(v) &&
-	    (v->flags & BRIDGE_VLAN_INFO_UNTAGGED))
-		return true;
-
-	return false;
+	return pvid && pvid->vid == vid && br_vlan_should_use(pvid) &&
+	       (pvid->flags & BRIDGE_VLAN_INFO_UNTAGGED);
 }
 
 static void br_vlan_disable_default_pvid(struct net_bridge *br)
@@ -1524,7 +1553,7 @@ int br_vlan_get_info(const struct net_device *dev, u16 vid,
 
 	p_vinfo->vid = vid;
 	p_vinfo->flags = v->flags;
-	if (vid == br_get_pvid(vg))
+	if (v == rcu_access_pointer(vg->pvid))
 		p_vinfo->flags |= BRIDGE_VLAN_INFO_PVID;
 	return 0;
 }
@@ -1551,7 +1580,7 @@ int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
 
 	p_vinfo->vid = vid;
 	p_vinfo->flags = READ_ONCE(v->flags);
-	if (vid == br_get_pvid(vg))
+	if (v == rcu_access_pointer(vg->pvid))
 		p_vinfo->flags |= BRIDGE_VLAN_INFO_PVID;
 	return 0;
 }
@@ -1956,7 +1985,7 @@ void br_vlan_notify(const struct net_bridge *br,
 			goto out_kfree;
 
 		flags = v->flags;
-		if (br_get_pvid(vg) == v->vid)
+		if (v == rcu_access_pointer(vg->pvid))
 			flags |= BRIDGE_VLAN_INFO_PVID;
 		break;
 	case RTM_DELVLAN:
diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
index 506668fc31d9d..e4b7a147e42b2 100644
--- a/net/bridge/br_vlan_options.c
+++ b/net/bridge/br_vlan_options.c
@@ -110,8 +110,7 @@ size_t br_vlan_opts_nl_size(void)
 	       + 0;
 }
 
-static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
-				struct net_bridge_vlan *v,
+static int br_vlan_modify_state(struct net_bridge_vlan *v,
 				u8 state,
 				bool *changed,
 				struct netlink_ext_ack *extack)
@@ -143,9 +142,6 @@ static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
 	if (v->state == state)
 		return 0;
 
-	if (v->vid == br_get_pvid(vg))
-		br_vlan_set_pvid_state(vg, state);
-
 	br_vlan_set_state(v, state);
 	*changed = true;
 
@@ -216,7 +212,6 @@ static int br_vlan_modify_tunnel(const struct net_bridge_port *p,
 
 static int br_vlan_process_one_opts(const struct net_bridge *br,
 				    const struct net_bridge_port *p,
-				    struct net_bridge_vlan_group *vg,
 				    struct net_bridge_vlan *v,
 				    struct nlattr **tb,
 				    bool *changed,
@@ -228,7 +223,7 @@ static int br_vlan_process_one_opts(const struct net_bridge *br,
 	if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
 		u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
 
-		err = br_vlan_modify_state(vg, v, state, changed, extack);
+		err = br_vlan_modify_state(v, state, changed, extack);
 		if (err)
 			return err;
 	}
@@ -339,8 +334,7 @@ int br_vlan_process_options(const struct net_bridge *br,
 			break;
 		}
 
-		err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
-					       extack);
+		err = br_vlan_process_one_opts(br, p, v, tb, &changed, extack);
 		if (err)
 			break;
 

23/3 2026/09/20 21:30 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 17957
							output: 36
							thoughts: 602
						

Thoughts:
**KMSAN Patch Evaluation**

Okay, so I need to decide if this patch warrants a dedicated KMSAN fuzzing session. Let's see... the core issue KMSAN hunts down is uninitialized memory reads, right? Specifically, we're looking for kernel code potentially leaking uninitialized data to user space or reading uninitialized data internally. The rules are pretty clear: any changes introducing or modifying kernel structures sent to user space, conditional logic relying on uninitialized variables, complex data structure initialization with potential for uninitialized fields being read later, or changes in bounds checks that might allow uninitialized bytes to be accessed. I know KASAN covers other memory safety issues like OOB, UAF, and double-frees, so I don't need to worry about that here.

Let's dive into the patch. Seems like it primarily affects the bridge networking code.

I see changes in `net/bridge/br_arp_nd_proxy.c`, with new and changed function signatures in the `br_is_neigh_*` functions. We've got `br_is_neigh_suppress_enabled_vid` being added, and changes to the parameters being passed to those functions. Now, instead of passing `u16 vid`, they're passing `const struct net_bridge_vlan *v`. That's interesting, but I need to understand if this introduces a KMSAN risk. Is the `vlan` struct initialized before being used, or could we end up reading uninitialized data?

Now, let's look at `net/bridge/br_device.c`. Inside `br_dev_xmit`, instead of passing `vid` we are passing `vlan` into the `br_flood` function. Where is this `vlan` coming from? I need to track it down. My gut says this could be a point of concern. The critical question: Is `vlan` properly initialized *before* it gets passed? I need to understand what's happening around line 89 in that file to be sure. Time to pull that up.



24/3 2026/09/20 21:30 tool read-file
0m Args:
{
  "File": "net/bridge/br_device.c",
  "FirstLine": 40,
  "LineCount": 80
}

Results:
{
  "Contents": "  40:\t\tstruct net_bridge_vlan *vlan;\n  41:\t\tconst unsigned char *dest;\n  42:\t\tu16 vid = 0;\n  43:\t\n  44:\t\tif (unlikely(reason != SKB_NOT_DROPPED_YET)) {\n  45:\t\t\tkfree_skb_reason(skb, reason);\n  46:\t\t\treturn NETDEV_TX_OK;\n  47:\t\t}\n  48:\t\n  49:\t\tmemset(skb-\u003ecb, 0, sizeof(struct br_input_skb_cb));\n  50:\t\tbr_tc_skb_miss_set(skb, false);\n  51:\t\n  52:\t\trcu_read_lock();\n  53:\t\tnf_ops = rcu_dereference(nf_br_ops);\n  54:\t\tif (nf_ops \u0026\u0026 nf_ops-\u003ebr_dev_xmit_hook(skb)) {\n  55:\t\t\trcu_read_unlock();\n  56:\t\t\treturn NETDEV_TX_OK;\n  57:\t\t}\n  58:\t\n  59:\t\tdev_sw_netstats_tx_add(dev, 1, skb-\u003elen);\n  60:\t\n  61:\t\tbr_switchdev_frame_unmark(skb);\n  62:\t\tBR_INPUT_SKB_CB(skb)-\u003ebrdev = dev;\n  63:\t\tBR_INPUT_SKB_CB(skb)-\u003efrag_max_size = 0;\n  64:\t\n  65:\t\tskb_reset_mac_header(skb);\n  66:\t\tskb_pull(skb, ETH_HLEN);\n  67:\t\n  68:\t\tif (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, \u0026vid,\n  69:\t\t\t\t\t\u0026state, \u0026vlan))\n  70:\t\t\tgoto out;\n  71:\t\n  72:\t\tif (IS_ENABLED(CONFIG_INET) \u0026\u0026\n  73:\t\t    (eth_hdr(skb)-\u003eh_proto == htons(ETH_P_ARP) ||\n  74:\t\t     eth_hdr(skb)-\u003eh_proto == htons(ETH_P_RARP)) \u0026\u0026\n  75:\t\t    br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED)) {\n  76:\t\t\tbr_do_proxy_suppress_arp(skb, br, vid, NULL);\n  77:\t\t} else if (ipv6_mod_enabled() \u0026\u0026\n  78:\t\t\t   skb-\u003eprotocol == htons(ETH_P_IPV6) \u0026\u0026\n  79:\t\t\t   br_opt_get(br, BROPT_NEIGH_SUPPRESS_ENABLED) \u0026\u0026\n  80:\t\t\t   pskb_may_pull(skb, sizeof(struct ipv6hdr) +\n  81:\t\t\t\t\t sizeof(struct nd_msg)) \u0026\u0026\n  82:\t\t\t   ipv6_hdr(skb)-\u003enexthdr == IPPROTO_ICMPV6) {\n  83:\t\t\t\tstruct nd_msg *msg;\n  84:\t\n  85:\t\t\t\tmsg = br_is_nd_neigh_msg(skb);\n  86:\t\t\t\tif (msg)\n  87:\t\t\t\t\tbr_do_suppress_nd(skb, br, vid, NULL, msg);\n  88:\t\t}\n  89:\t\n  90:\t\tdest = eth_hdr(skb)-\u003eh_dest;\n  91:\t\tif (is_broadcast_ether_addr(dest)) {\n  92:\t\t\tbr_flood(br, vlan, skb, BR_PKT_BROADCAST, false, true);\n  93:\t\t} else if (is_multicast_ether_addr(dest)) {\n  94:\t\t\tif (unlikely(netpoll_tx_running(dev))) {\n  95:\t\t\t\tbr_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);\n  96:\t\t\t\tgoto out;\n  97:\t\t\t}\n  98:\t\t\tif (br_multicast_rcv(\u0026brmctx, \u0026pmctx_null, vlan, skb, vid)) {\n  99:\t\t\t\tkfree_skb(skb);\n 100:\t\t\t\tgoto out;\n 101:\t\t\t}\n 102:\t\n 103:\t\t\tmdst = br_mdb_entry_skb_get(brmctx, skb, vid);\n 104:\t\t\tif ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) \u0026\u0026\n 105:\t\t\t    br_multicast_querier_exists(brmctx, eth_hdr(skb), mdst))\n 106:\t\t\t\tbr_multicast_flood(mdst, skb, brmctx, false, true);\n 107:\t\t\telse\n 108:\t\t\t\tbr_flood(br, vlan, skb, BR_PKT_MULTICAST, false, true);\n 109:\t\t} else if ((dst = br_fdb_find_rcu(br, dest, vid)) != NULL) {\n 110:\t\t\tbr_forward(READ_ONCE(dst-\u003edst), skb, false, true);\n 111:\t\t} else {\n 112:\t\t\tbr_flood(br, vlan, skb, BR_PKT_UNICAST, false, true);\n 113:\t\t}\n 114:\tout:\n 115:\t\trcu_read_unlock();\n 116:\t\treturn NETDEV_TX_OK;\n 117:\t}\n 118:\t\n 119:\tstatic int br_dev_init(struct net_device *dev)\n"
}

25/3 2026/09/20 21:30 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 20470
							output: 28
							thoughts: 92
						

Thoughts:
**Analyzing VLAN Initialization in `br_dev_xmit`**

Okay, let's break this down. I'm looking at `br_dev_xmit` and immediately see `br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid, &state, &vlan)`. The key thing here is the initialization of `vlan`. Specifically, if VLANs are *disabled* – that is, `br_vlan_enabled` is false – what value does `br_allowed_ingress` assign to `vlan`? That's the crucial point. We need to determine the default behavior when VLAN tagging isn't active. Time to dive into `br_allowed_ingress` and find out.



26/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "br_allowed_ingress",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_device.c=30=netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)\n--\nnet/bridge/br_device.c-67-\nnet/bridge/br_device.c:68:\tif (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, \u0026vid,\nnet/bridge/br_device.c-69-\t\t\t\t\u0026state, \u0026vlan))\n--\nnet/bridge/br_input.c=76=int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb)\n--\nnet/bridge/br_input.c-109-\tpmctx = \u0026p-\u003emulticast_ctx;\nnet/bridge/br_input.c:110:\tif (!br_allowed_ingress(p-\u003ebr, nbp_vlan_group_rcu(p), skb, \u0026vid,\nnet/bridge/br_input.c-111-\t\t\t\t\u0026state, \u0026vlan))\n--\nnet/bridge/br_private.h=1590=br_multicast_ctx_options_equal(const struct net_bridge_mcast *brmctx1,\n--\nnet/bridge/br_private.h-1598-#ifdef CONFIG_BRIDGE_VLAN_FILTERING\nnet/bridge/br_private.h:1599:bool br_allowed_ingress(const struct net_bridge *br,\nnet/bridge/br_private.h-1600-\t\t\tstruct net_bridge_vlan_group *vg, struct sk_buff *skb,\n--\nnet/bridge/br_private.h=1716=static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)\n--\nnet/bridge/br_private.h-1720-#else\nnet/bridge/br_private.h:1721:static inline bool br_allowed_ingress(const struct net_bridge *br,\nnet/bridge/br_private.h-1722-\t\t\t\t      struct net_bridge_vlan_group *vg,\n--\nnet/bridge/br_vlan.c=584=static bool __allowed_ingress(const struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-680-\nnet/bridge/br_vlan.c:681:bool br_allowed_ingress(const struct net_bridge *br,\nnet/bridge/br_vlan.c-682-\t\t\tstruct net_bridge_vlan_group *vg, struct sk_buff *skb,\n"
}

27/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 21420
							output: 38
							thoughts: 28
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Checking Ingress Rules**

Right, let's just make absolutely sure we haven't missed anything in the ingress filtering logic. I'm going to quickly double-check `br_allowed_ingress`. It's vital to ensure it's defined correctly within `br_private.h`, so I'll start there. We need to be airtight on this, particularly with the more complex VLAN setups we've been working on. After that, I'll jump over to `br_vlan.c` to see how those ingress permissions are *actually* being applied in the VLAN context. We can't afford any security holes or unexpected traffic flows, so a quick cross-reference of those two files is in order. It's a fundamental sanity check, but a necessary one to confirm proper handling.



28/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 670,
  "LineCount": 40
}

Results:
{
  "Contents": " 670:\t\t}\n 671:\t\n 672:\t\t*vlan = v;\n 673:\t\n 674:\t\treturn true;\n 675:\t\n 676:\tdrop:\n 677:\t\tkfree_skb(skb);\n 678:\t\treturn false;\n 679:\t}\n 680:\t\n 681:\tbool br_allowed_ingress(const struct net_bridge *br,\n 682:\t\t\t\tstruct net_bridge_vlan_group *vg, struct sk_buff *skb,\n 683:\t\t\t\tu16 *vid, u8 *state,\n 684:\t\t\t\tstruct net_bridge_vlan **vlan)\n 685:\t{\n 686:\t\t/* If VLAN filtering is disabled on the bridge, all packets are\n 687:\t\t * permitted.\n 688:\t\t */\n 689:\t\t*vlan = NULL;\n 690:\t\tif (!br_opt_get(br, BROPT_VLAN_ENABLED)) {\n 691:\t\t\tBR_INPUT_SKB_CB(skb)-\u003evlan_filtered = false;\n 692:\t\t\treturn true;\n 693:\t\t}\n 694:\t\n 695:\t\treturn __allowed_ingress(br, vg, skb, vid, state, vlan);\n 696:\t}\n 697:\t\n 698:\t/* Called under RCU. */\n 699:\tbool br_allowed_egress(struct net_bridge_vlan_group *vg,\n 700:\t\t\t       const struct sk_buff *skb)\n 701:\t{\n 702:\t\tconst struct net_bridge_vlan *v;\n 703:\t\tu16 vid;\n 704:\t\n 705:\t\t/* If this packet was not filtered at input, let it pass */\n 706:\t\tif (!BR_INPUT_SKB_CB(skb)-\u003evlan_filtered)\n 707:\t\t\treturn true;\n 708:\t\n 709:\t\tbr_vlan_get_tag(skb, \u0026vid);\n"
}

29/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22241
							output: 38
							thoughts: 27
						

Thoughts:
Here's my take on those thoughts:

**Analyzing `br_allowed_ingress` in `br_private.h`**

Alright, let's dive into this. I'm taking a look at this `br_private.h` file, specifically focusing on the `#else` definition for `br_allowed_ingress`. Given my expertise, I'm immediately thinking about how this relates to ingress filtering, security policies, and perhaps even the underlying hardware capabilities of the bridge. I need to understand what this conditional branch signifies. What scenarios trigger the `#else` and what implications does that have for packet forwarding? Is this related to a specific hardware platform or a software-defined configuration? Are there any performance trade-offs associated with this configuration? I need to analyze the code block within the `#else` to fully grasp its functionality and its impact on the overall bridging behavior. This will likely provide valuable insights into the flexibility or limitations of this component.



30/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_private.h",
  "FirstLine": 1720,
  "LineCount": 25
}

Results:
{
  "Contents": "1720:\t#else\n1721:\tstatic inline bool br_allowed_ingress(const struct net_bridge *br,\n1722:\t\t\t\t\t      struct net_bridge_vlan_group *vg,\n1723:\t\t\t\t\t      struct sk_buff *skb,\n1724:\t\t\t\t\t      u16 *vid, u8 *state,\n1725:\t\t\t\t\t      struct net_bridge_vlan **vlan)\n1726:\t\n1727:\t{\n1728:\t\t*vlan = NULL;\n1729:\t\treturn true;\n1730:\t}\n1731:\t\n1732:\tstatic inline bool br_allowed_egress(struct net_bridge_vlan_group *vg,\n1733:\t\t\t\t\t     const struct sk_buff *skb)\n1734:\t{\n1735:\t\treturn true;\n1736:\t}\n1737:\t\n1738:\tstatic inline bool br_should_learn(struct net_bridge_port *p,\n1739:\t\t\t\t\t   struct sk_buff *skb, u16 *vid)\n1740:\t{\n1741:\t\treturn true;\n1742:\t}\n1743:\t\n1744:\tstatic inline struct sk_buff *br_handle_vlan(struct net_bridge *br,\n"
}

31/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 22864
							output: 26
							thoughts: 1937
						

Thoughts:
**Analyzing VLAN Forwarding and Potential Uninitialized Memory Access**

Alright, let's break this down systematically. My initial observation is that in the `br_dev_xmit` path, `vlan` is always initialized by `br_allowed_ingress`. Now, diving deeper into these code changes...

First, I examine `br_forward.c`. I see `struct br_fwd_dst` and how it's used within `br_forward`, `br_flood`, `br_flood_vlan`, `maybe_deliver`, `maybe_deliver_addr`, and `br_multicast_flood`. In most of these instances, both `port` and `vlan` within `br_fwd_dst` are consistently initialized, either explicitly or implicitly through zero-initialization (`{}`). `br_flood_vlan` stands out, where `dst.port` and `dst.vlan` are definitely initialized before being passed to `br_flood_port`. `maybe_deliver` overwrites `prev` with `fwd` in its entirety, which is safe assuming both structs share the same field ordering.

However, a red flag appeared in `br_vlan_rebuild_port_array`. Here, `kvmalloc` is used to allocate memory for the `net_bridge_vlan_port_array`. And the key detail is that `kvmalloc` does *not* zero the allocated memory. I need to understand if there is padding or uninitialized memory in this structure.

So, let's examine the structure itself: `struct net_bridge_vlan_port_array`. It includes a `rcu_head`, a `count` field, and a variable-length array of `net_bridge_vlan` pointers, `vlans[]`. On a 64-bit architecture, I can predict padding will arise in the structure. 

`rcu_head` is 16 bytes: `void *next; void (*func)(struct rcu_head *head);` (16 bytes). `unsigned int count;` is 4 bytes. The pointer array `struct net_bridge_vlan *vlans[];` is 8-byte aligned. Therefore, there are potentially 4 bytes of padding between the `count` field (offset 20) and the start of the `vlans` array (offset 24).

The concern is that if the memory allocated by `kvmalloc` is *not* zeroed, and if we're not careful, we could potentially read uninitialized memory from the `vlans` array. This is a potential vulnerability. To verify this, I need to check whether `struct net_bridge_vlan_port_array` ever gets copied to userspace or read, if so, the vulnerability has more impact. I need to do a full code search to see where `port_array` is being accessed. This is the next step to confirm this is a real problem.



32/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "port_array",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_forward.c=286=static int br_flood_vlan(struct br_fwd_dst *prev,\n--\nnet/bridge/br_forward.c-289-{\nnet/bridge/br_forward.c:290:\tstruct net_bridge_vlan_port_array *array;\nnet/bridge/br_forward.c-291-\tstruct net_bridge_vlan *masterv, *pv;\n--\nnet/bridge/br_forward.c-295-\tmasterv = br_vlan_is_master(v) ? v : v-\u003ebrvlan;\nnet/bridge/br_forward.c:296:\tarray = rcu_dereference(masterv-\u003eport_array);\nnet/bridge/br_forward.c-297-\tif (array) {\n--\nnet/bridge/br_private.h=183=enum {\n--\nnet/bridge/br_private.h-197-\nnet/bridge/br_private.h:198:struct net_bridge_vlan_port_array {\nnet/bridge/br_private.h-199-\tstruct rcu_head\t\trcu;\n--\nnet/bridge/br_private.h-223- * @msti: if MASTER flag set, this holds the VLANs MST instance\nnet/bridge/br_private.h:224: * @port_array: if MASTER flag set, this is the port-VLAN array\nnet/bridge/br_private.h-225- * @port_vlist: if MASTER flag set, this is the port-VLAN list\n--\nnet/bridge/br_private.h=234=struct net_bridge_vlan {\n--\nnet/bridge/br_private.h-259-\nnet/bridge/br_private.h:260:\tstruct net_bridge_vlan_port_array __rcu *port_array;\nnet/bridge/br_private.h-261-\tstruct list_head\t\tport_vlist;\n--\nnet/bridge/br_vlan.c=261=static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)\n--\nnet/bridge/br_vlan.c-265-\nnet/bridge/br_vlan.c:266:static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,\nnet/bridge/br_vlan.c-267-\t\t\t\t       unsigned int count)\nnet/bridge/br_vlan.c-268-{\nnet/bridge/br_vlan.c:269:\tstruct net_bridge_vlan_port_array *array = NULL, *old;\nnet/bridge/br_vlan.c-270-\tunsigned int i = 0;\n--\nnet/bridge/br_vlan.c-284-\nnet/bridge/br_vlan.c:285:\told = rtnl_dereference(masterv-\u003eport_array);\nnet/bridge/br_vlan.c:286:\trcu_assign_pointer(masterv-\u003eport_array, array);\nnet/bridge/br_vlan.c-287-\tif (old)\n--\nnet/bridge/br_vlan.c=302=static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-401-\tif (p) {\nnet/bridge/br_vlan.c:402:\t\tbr_vlan_rebuild_port_array(masterv, br_vlan_num_ports(masterv));\nnet/bridge/br_vlan.c-403-\t\tnbp_vlan_set_vlan_dev_state(p, v-\u003evid);\n--\nnet/bridge/br_vlan.c=432=static void __vlan_del(struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c-473-\t\t/* -1 because br_vlan_put_master() is called later */\nnet/bridge/br_vlan.c:474:\t\tbr_vlan_rebuild_port_array(masterv,\nnet/bridge/br_vlan.c-475-\t\t\t\t\t   br_vlan_num_ports(masterv) - 1);\n"
}

33/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26561
							output: 27
							thoughts: 203
						

Thoughts:
**Investigating a Potential Bug in VLAN Port Rebuilding**

Okay, here's what's running through my head. I'm looking at this `br_vlan_rebuild_port_array` function, and I'm immediately struck by something potentially problematic: the use of `count` versus `i`. My initial gut feeling is that something isn't quite right.

Hold on a second! Could it be that `count` is somehow *larger* than the actual number of elements present in `masterv->port_vlist`? That's a classic off-by-one or out-of-bounds error scenario, and it would wreak havoc. We can't let that happen!

Alright, time to get to the bottom of this. Let's start with a crucial check. This function looks like it might hold the key:

```c
static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)
{
	return refcount_read(&masterv->refcnt) - br_vlan_is_brentry(masterv);
}
```

Okay, so `br_vlan_num_ports` seems to be responsible for calculating the "number of ports" associated with the vlan. But that still begs the question. What exactly is `masterv->refcnt`, and how is it related to the elements in `masterv->port_vlist`? And what *is* `masterv->port_vlist` itself? The code's going to be the truth-teller.

Right, I need to understand how the `refcnt` is incremented and decremented, and how entries are added and removed from that `port_vlist`. Time to dive into the source code and start searching for all instances of `port_vlist` within `net/bridge/br_vlan.c`. That's where the answers are going to be. Let's go!



34/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "port_vlist",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_forward.c=286=static int br_flood_vlan(struct br_fwd_dst *prev,\n--\nnet/bridge/br_forward.c-309-\t} else {\nnet/bridge/br_forward.c:310:\t\tlist_for_each_entry_rcu(pv, \u0026masterv-\u003eport_vlist, port_vlist) {\nnet/bridge/br_forward.c-311-\t\t\tdst.port = pv-\u003eport;\n--\nnet/bridge/br_private.h=198=struct net_bridge_vlan_port_array {\n--\nnet/bridge/br_private.h-224- * @port_array: if MASTER flag set, this is the port-VLAN array\nnet/bridge/br_private.h:225: * @port_vlist: if MASTER flag set, this is the port-VLAN list\nnet/bridge/br_private.h-226- * @vlist: sorted list of VLAN entries\n--\nnet/bridge/br_private.h=234=struct net_bridge_vlan {\n--\nnet/bridge/br_private.h-260-\tstruct net_bridge_vlan_port_array __rcu *port_array;\nnet/bridge/br_private.h:261:\tstruct list_head\t\tport_vlist;\nnet/bridge/br_private.h-262-\tstruct list_head\t\tvlist;\n--\nnet/bridge/br_vlan.c=127=static void __vlan_add_list(struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c-136-\t\tvg = nbp_vlan_group(v-\u003eport);\nnet/bridge/br_vlan.c:137:\t\tlist_add_rcu(\u0026v-\u003eport_vlist, \u0026v-\u003ebrvlan-\u003eport_vlist);\nnet/bridge/br_vlan.c-138-\t}\n--\nnet/bridge/br_vlan.c=149=static void __vlan_del_list(struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c-152-\tif (br_vlan_is_master(v))\nnet/bridge/br_vlan.c:153:\t\tWARN_ON(!list_empty(\u0026v-\u003eport_vlist));\nnet/bridge/br_vlan.c-154-\telse\nnet/bridge/br_vlan.c:155:\t\tlist_del_rcu(\u0026v-\u003eport_vlist);\nnet/bridge/br_vlan.c-156-}\n--\nnet/bridge/br_vlan.c=266=static void br_vlan_rebuild_port_array(struct net_bridge_vlan *masterv,\n--\nnet/bridge/br_vlan.c-280-\t\tarray-\u003ecount = count;\nnet/bridge/br_vlan.c:281:\t\tlist_for_each_entry(pv, \u0026masterv-\u003eport_vlist, port_vlist)\nnet/bridge/br_vlan.c-282-\t\t\tarray-\u003evlans[i++] = pv;\n--\nnet/bridge/br_vlan.c=302=static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-373-\t\tv-\u003epriv_flags |= BR_VLFLAG_GLOBAL_MCAST_ENABLED;\nnet/bridge/br_vlan.c:374:\t\tINIT_LIST_HEAD(\u0026v-\u003eport_vlist);\nnet/bridge/br_vlan.c-375-\t}\n"
}

35/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28149
							output: 45
							thoughts: 56
						

Thoughts:
**Investigating the Reference Count of `masterv`**

Okay, let's dive in. My immediate concern is understanding the lifecycle of this `masterv` object, specifically its reference count (`refcnt`). I need to determine precisely when this counter is incremented, and crucially, when and how it's decremented. This is fundamental to avoiding memory leaks or, conversely, premature freeing of the resource. To do this properly, I will need to search the code base for any instances where the `refcnt` variable associated with `masterv` is directly modified.

I’ll broaden the search scope, just in case there are any indirect manipulations of the reference counter. I also want to look for usages of the helper functions `br_vlan_get_master` and `br_vlan_put_master`, as they are highly likely to be the entry points for modifying the reference counter. This thorough approach should expose the complete picture of how `masterv` is managed.



36/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "master.*refcnt|br_vlan_get_master|br_vlan_put_master",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=175=static struct net_bridge_vlan *\nnet/bridge/br_vlan.c:176:br_vlan_get_master(struct net_bridge *br, u16 vid,\nnet/bridge/br_vlan.c-177-\t\t   struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c-192-\t\t\treturn NULL;\nnet/bridge/br_vlan.c:193:\t\trefcount_set(\u0026masterv-\u003erefcnt, 1);\nnet/bridge/br_vlan.c-194-\t\treturn masterv;\nnet/bridge/br_vlan.c-195-\t}\nnet/bridge/br_vlan.c:196:\trefcount_inc(\u0026masterv-\u003erefcnt);\nnet/bridge/br_vlan.c-197-\n--\nnet/bridge/br_vlan.c=201=static void br_master_vlan_rcu_free(struct rcu_head *rcu)\n--\nnet/bridge/br_vlan.c-211-\nnet/bridge/br_vlan.c:212:static void br_vlan_put_master(struct net_bridge_vlan *masterv)\nnet/bridge/br_vlan.c-213-{\n--\nnet/bridge/br_vlan.c-219-\tvg = br_vlan_group(masterv-\u003ebr);\nnet/bridge/br_vlan.c:220:\tif (refcount_dec_and_test(\u0026masterv-\u003erefcnt)) {\nnet/bridge/br_vlan.c-221-\t\trhashtable_remove_fast(\u0026vg-\u003evlan_hash,\n--\nnet/bridge/br_vlan.c=261=static unsigned int br_vlan_num_ports(const struct net_bridge_vlan *masterv)\nnet/bridge/br_vlan.c-262-{\nnet/bridge/br_vlan.c:263:\treturn refcount_read(\u0026masterv-\u003erefcnt) - br_vlan_is_brentry(masterv);\nnet/bridge/br_vlan.c-264-}\n--\nnet/bridge/br_vlan.c=302=static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-346-\nnet/bridge/br_vlan.c:347:\t\tmasterv = br_vlan_get_master(br, v-\u003evid, extack);\nnet/bridge/br_vlan.c-348-\t\tif (!masterv) {\n--\nnet/bridge/br_vlan.c-421-\nnet/bridge/br_vlan.c:422:\t\t\tbr_vlan_put_master(masterv);\nnet/bridge/br_vlan.c-423-\t\t\tv-\u003ebrvlan = NULL;\n--\nnet/bridge/br_vlan.c=432=static void __vlan_del(struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c-472-\t\t__vlan_del_list(v);\nnet/bridge/br_vlan.c:473:\t\t/* -1 because br_vlan_put_master() is called later */\nnet/bridge/br_vlan.c-474-\t\tbr_vlan_rebuild_port_array(masterv,\n--\nnet/bridge/br_vlan.c-481-\nnet/bridge/br_vlan.c:482:\tbr_vlan_put_master(masterv);\nnet/bridge/br_vlan.c-483-}\n"
}

37/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 29412
							output: 46
							thoughts: 1148
						

Thoughts:
**Analyzing VLAN Port Array and Synchronization**

Okay, let's break this down. My initial focus is on the relationship between `masterv->refcnt` and `masterv->port_vlist`. I need to understand how these interact when a port VLAN is added. My understanding is:

1.  When a port VLAN is added, the code flow goes through `__vlan_add`, where it checks if a port exists.
2.  `masterv` is obtained via `br_vlan_get_master`. Then `__vlan_add_list` is called.
3.  `__vlan_add_list` adds the VLAN to `port_vlist` using `list_add_rcu`.
4.  Then, `br_vlan_rebuild_port_array` is called to rebuild the port array, and `nbp_vlan_set_vlan_dev_state` sets up the VLAN device state for the given port.
5.  `br_vlan_num_ports` calculates the number of ports, which is `refcount_read(&masterv->refcnt) - br_vlan_is_brentry(masterv)`.

So, when `masterv` is created: `br_vlan_get_master` sets `masterv->refcnt = 1`. Then `br_vlan_is_brentry(masterv)` is 1 (if bridge device is in vlan). If a port is added, `br_vlan_get_master` does `refcount_inc`.

With this in mind, `refcnt` is 2, and `br_vlan_is_brentry` is 1, so `br_vlan_num_ports` = 2 - 1 = 1, and `list_add_rcu` added 1 element to `port_vlist`. So the number of elements in `port_vlist` matches the count.

Now, what if `count` exceeds `BR_VLAN_PORT_ARRAY_THRESHOLD` (8)? In that case, an array is allocated using `kvmalloc`. The array is populated by traversing the `port_vlist` and copying the pointers to `vlans[]`. I need to ensure that when `i == count`, all `count` pointers are written. If `i != count` (which isn't possible given the loop conditions), there would be a problem. This is a potential area for a bug, but I need to prove it is an issue.

Then I am thinking about how these operations are synchronized, and I see all operations on `masterv` happen under RTNL, with `br_vlan_add` and `br_vlan_del` being called under the RTNL lock. I will verify if `br_vlan_rebuild_port_array` is also called under RTNL by using `rtnl_dereference`. I can see line 285: `old = rtnl_dereference(masterv->port_array)`. This indicates that the code confirms that `rtnl_is_locked()` is true, and confirms that `br_vlan_rebuild_port_array` is serialized under the RTNL lock. That means the `list_for_each_entry` traverses `port_vlist` under RTNL. Each port VLAN that incremented `refcnt` is in `port_vlist`, so the number of elements in `port_vlist` equals `count`.

Now, I also need to think about how `array` is read in `br_flood_vlan`. The code retrieves `array` using `rcu_dereference`, then iterates up to `array->count`. This protects against reads while the array is being updated. The loop condition `i < array->count` ensures that it never reads beyond the array bounds. The code then uses `kvfree_rcu` to deallocate, so there aren't any memory leaks. It looks like the `array` is an internal kernel structure used within `br_flood_vlan` and RCU ensures proper port iteration for flooding, so it's all internal.

The final thing I see is that in `br_vlan.c`, the `pvid` in `net_bridge_vlan_group` has been changed from `u16 pvid; u8 pvid_state;` to `struct net_bridge_vlan __rcu *pvid;`. I need to check how `struct net_bridge_vlan_group` is allocated and see if there are any consequences of this change.



38/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "kzalloc.*net_bridge_vlan_group|kmalloc.*net_bridge_vlan_group",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=1283=int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c-1293-\nnet/bridge/br_vlan.c:1294:\tvg = kzalloc_obj(struct net_bridge_vlan_group);\nnet/bridge/br_vlan.c-1295-\tif (!vg)\n"
}

39/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31745
							output: 52
							thoughts: 23
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Zeroing Memory and VLAN Initialization**

Okay, so `kzalloc_obj`... that's the one that guarantees zeroed memory. Always good to double-check these things, especially when dealing with potentially sensitive data or control structures. Makes debugging *much* easier down the line.

Now, let's pivot to `br_vlan_init`. I need to understand how the VLAN initialization is handled in this context. What data structures are they setting up? What's the initialization sequence? Are there any potential race conditions I need to be aware of? I'll probably want to trace the code path here to get a good handle on how they're handling VLAN configuration and state management. Is it using any standard VLAN APIs or some custom implementation? This is crucial for performance and compatibility. I need to delve into the initialization to see how they integrate with the broader bridging framework.



40/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "kzalloc_obj.*struct net_bridge_vlan_group|kzalloc.*sizeof.*struct net_bridge_vlan_group",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=1283=int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c-1293-\nnet/bridge/br_vlan.c:1294:\tvg = kzalloc_obj(struct net_bridge_vlan_group);\nnet/bridge/br_vlan.c-1295-\tif (!vg)\n"
}

41/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32156
							output: 34
							thoughts: 15
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:

**Analyzing the VLAN Grouping on the Bridge**

Alright, before I dive deeper into troubleshooting this network behavior, I need a clear understanding of the foundation. Let's make sure I'm on solid ground with the basics. I'll need to immediately verify how the VLAN groups are actually allocated on this specific bridge. This is critical: I have to confirm the existing configuration before I can accurately diagnose any potential issues, or even accurately model its behavior for that matter. Jumping to conclusions without knowing the base VLAN layout could send me down a rabbit hole of misdirected effort. Knowing the VLAN configuration on the bridge is step one.



42/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "vlan_group",
  "PathPrefix": "net/bridge/br_vlan.c"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=32=static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)\n--\nnet/bridge/br_vlan.c-36-\nnet/bridge/br_vlan.c:37:static void __vlan_add_pvid(struct net_bridge_vlan_group *vg,\nnet/bridge/br_vlan.c-38-\t\t\t    struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c-45-\nnet/bridge/br_vlan.c:46:static void __vlan_delete_pvid(struct net_bridge_vlan_group *vg,\nnet/bridge/br_vlan.c-47-\t\t\t       struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c=59=static bool __vlan_flags_update(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-61-{\nnet/bridge/br_vlan.c:62:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-63-\tstruct net_bridge_vlan *pvid;\n--\nnet/bridge/br_vlan.c-67-\tif (br_vlan_is_master(v))\nnet/bridge/br_vlan.c:68:\t\tvg = br_vlan_group(v-\u003ebr);\nnet/bridge/br_vlan.c-69-\telse\nnet/bridge/br_vlan.c:70:\t\tvg = nbp_vlan_group(v-\u003eport);\nnet/bridge/br_vlan.c-71-\n--\nnet/bridge/br_vlan.c=127=static void __vlan_add_list(struct net_bridge_vlan *v)\nnet/bridge/br_vlan.c-128-{\nnet/bridge/br_vlan.c:129:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-130-\tstruct list_head *headp, *hpos;\n--\nnet/bridge/br_vlan.c-133-\tif (br_vlan_is_master(v)) {\nnet/bridge/br_vlan.c:134:\t\tvg = br_vlan_group(v-\u003ebr);\nnet/bridge/br_vlan.c-135-\t} else {\nnet/bridge/br_vlan.c:136:\t\tvg = nbp_vlan_group(v-\u003eport);\nnet/bridge/br_vlan.c-137-\t\tlist_add_rcu(\u0026v-\u003eport_vlist, \u0026v-\u003ebrvlan-\u003eport_vlist);\n--\nnet/bridge/br_vlan.c=176=br_vlan_get_master(struct net_bridge *br, u16 vid,\n--\nnet/bridge/br_vlan.c-178-{\nnet/bridge/br_vlan.c:179:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-180-\tstruct net_bridge_vlan *masterv;\nnet/bridge/br_vlan.c-181-\nnet/bridge/br_vlan.c:182:\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-183-\tmasterv = br_vlan_find(vg, vid);\n--\nnet/bridge/br_vlan.c=212=static void br_vlan_put_master(struct net_bridge_vlan *masterv)\nnet/bridge/br_vlan.c-213-{\nnet/bridge/br_vlan.c:214:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-215-\n--\nnet/bridge/br_vlan.c-218-\nnet/bridge/br_vlan.c:219:\tvg = br_vlan_group(masterv-\u003ebr);\nnet/bridge/br_vlan.c-220-\tif (refcount_dec_and_test(\u0026masterv-\u003erefcnt)) {\n--\nnet/bridge/br_vlan.c=302=static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-306-\tstruct net_bridge_port *p = NULL;\nnet/bridge/br_vlan.c:307:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-308-\tstruct net_device *dev;\n--\nnet/bridge/br_vlan.c-314-\t\tdev = br-\u003edev;\nnet/bridge/br_vlan.c:315:\t\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-316-\t} else {\n--\nnet/bridge/br_vlan.c-319-\t\tdev = p-\u003edev;\nnet/bridge/br_vlan.c:320:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-321-\t}\n--\nnet/bridge/br_vlan.c=432=static void __vlan_del(struct net_bridge_vlan *v)\n--\nnet/bridge/br_vlan.c-434-\tstruct net_bridge_vlan *masterv = v;\nnet/bridge/br_vlan.c:435:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-436-\tstruct net_bridge_port *p = NULL;\n--\nnet/bridge/br_vlan.c-439-\tif (br_vlan_is_master(v)) {\nnet/bridge/br_vlan.c:440:\t\tvg = br_vlan_group(v-\u003ebr);\nnet/bridge/br_vlan.c-441-\t} else {\nnet/bridge/br_vlan.c-442-\t\tp = v-\u003eport;\nnet/bridge/br_vlan.c:443:\t\tvg = nbp_vlan_group(v-\u003eport);\nnet/bridge/br_vlan.c-444-\t\tmasterv = v-\u003ebrvlan;\n--\nnet/bridge/br_vlan.c-484-\nnet/bridge/br_vlan.c:485:static void __vlan_group_free(struct net_bridge_vlan_group *vg)\nnet/bridge/br_vlan.c-486-{\n--\nnet/bridge/br_vlan.c=493=static void __vlan_flush(const struct net_bridge *br,\nnet/bridge/br_vlan.c-494-\t\t\t const struct net_bridge_port *p,\nnet/bridge/br_vlan.c:495:\t\t\t struct net_bridge_vlan_group *vg)\nnet/bridge/br_vlan.c-496-{\n--\nnet/bridge/br_vlan.c=520=struct sk_buff *br_handle_vlan(struct net_bridge *br,\nnet/bridge/br_vlan.c-521-\t\t\t       const struct net_bridge_port *p,\nnet/bridge/br_vlan.c:522:\t\t\t       struct net_bridge_vlan_group *vg,\nnet/bridge/br_vlan.c-523-\t\t\t       struct net_bridge_vlan *v,\n--\nnet/bridge/br_vlan.c=584=static bool __allowed_ingress(const struct net_bridge *br,\nnet/bridge/br_vlan.c:585:\t\t\t      struct net_bridge_vlan_group *vg,\nnet/bridge/br_vlan.c-586-\t\t\t      struct sk_buff *skb, u16 *vid,\n--\nnet/bridge/br_vlan.c=681=bool br_allowed_ingress(const struct net_bridge *br,\nnet/bridge/br_vlan.c:682:\t\t\tstruct net_bridge_vlan_group *vg, struct sk_buff *skb,\nnet/bridge/br_vlan.c-683-\t\t\tu16 *vid, u8 *state,\n--\nnet/bridge/br_vlan.c-698-/* Called under RCU. */\nnet/bridge/br_vlan.c:699:bool br_allowed_egress(struct net_bridge_vlan_group *vg,\nnet/bridge/br_vlan.c-700-\t\t       const struct sk_buff *skb)\n--\nnet/bridge/br_vlan.c=719=bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid)\nnet/bridge/br_vlan.c-720-{\nnet/bridge/br_vlan.c:721:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-722-\tstruct net_bridge *br = p-\u003ebr;\n--\nnet/bridge/br_vlan.c-728-\nnet/bridge/br_vlan.c:729:\tvg = nbp_vlan_group_rcu(p);\nnet/bridge/br_vlan.c-730-\tif (!vg || !READ_ONCE(vg-\u003enum_vlans))\n--\nnet/bridge/br_vlan.c=751=static int br_vlan_add_existing(struct net_bridge *br,\nnet/bridge/br_vlan.c:752:\t\t\t\tstruct net_bridge_vlan_group *vg,\nnet/bridge/br_vlan.c-753-\t\t\t\tstruct net_bridge_vlan *vlan,\n--\nnet/bridge/br_vlan.c=811=int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,\n--\nnet/bridge/br_vlan.c-813-{\nnet/bridge/br_vlan.c:814:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-815-\tstruct net_bridge_vlan *vlan;\n--\nnet/bridge/br_vlan.c-820-\t*changed = false;\nnet/bridge/br_vlan.c:821:\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-822-\tvlan = br_vlan_find(vg, vid);\n--\nnet/bridge/br_vlan.c=856=int br_vlan_delete(struct net_bridge *br, u16 vid)\nnet/bridge/br_vlan.c-857-{\nnet/bridge/br_vlan.c:858:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-859-\tstruct net_bridge_vlan *v;\n--\nnet/bridge/br_vlan.c-862-\nnet/bridge/br_vlan.c:863:\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-864-\tv = br_vlan_find(vg, vid);\n--\nnet/bridge/br_vlan.c=877=void br_vlan_flush(struct net_bridge *br)\nnet/bridge/br_vlan.c-878-{\nnet/bridge/br_vlan.c:879:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-880-\n--\nnet/bridge/br_vlan.c-882-\nnet/bridge/br_vlan.c:883:\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-884-\t__vlan_flush(br, NULL, vg);\n--\nnet/bridge/br_vlan.c-886-\tsynchronize_net();\nnet/bridge/br_vlan.c:887:\t__vlan_group_free(vg);\nnet/bridge/br_vlan.c-888-}\nnet/bridge/br_vlan.c-889-\nnet/bridge/br_vlan.c:890:struct net_bridge_vlan *br_vlan_find(struct net_bridge_vlan_group *vg, u16 vid)\nnet/bridge/br_vlan.c-891-{\n--\nnet/bridge/br_vlan.c=978=int __br_vlan_set_proto(struct net_bridge *br, __be16 proto,\n--\nnet/bridge/br_vlan.c-989-\tstruct net_bridge_vlan *vlan;\nnet/bridge/br_vlan.c:990:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-991-\t__be16 oldproto = br-\u003evlan_proto;\n--\nnet/bridge/br_vlan.c-1001-\tlist_for_each_entry(p, \u0026br-\u003eport_list, list) {\nnet/bridge/br_vlan.c:1002:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1003-\t\tlist_for_each_entry(vlan, \u0026vg-\u003evlan_list, vlist) {\n--\nnet/bridge/br_vlan.c-1018-\tlist_for_each_entry(p, \u0026br-\u003eport_list, list) {\nnet/bridge/br_vlan.c:1019:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1020-\t\tlist_for_each_entry(vlan, \u0026vg-\u003evlan_list, vlist) {\n--\nnet/bridge/br_vlan.c-1039-\tlist_for_each_entry_continue_reverse(p, \u0026br-\u003eport_list, list) {\nnet/bridge/br_vlan.c:1040:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1041-\t\tlist_for_each_entry(vlan, \u0026vg-\u003evlan_list, vlist) {\n--\nnet/bridge/br_vlan.c=1074=int br_vlan_set_stats_per_port(struct net_bridge *br, unsigned long val)\n--\nnet/bridge/br_vlan.c-1079-\tlist_for_each_entry(p, \u0026br-\u003eport_list, list) {\nnet/bridge/br_vlan.c:1080:\t\tstruct net_bridge_vlan_group *vg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1081-\n--\nnet/bridge/br_vlan.c-1097-\nnet/bridge/br_vlan.c:1098:static bool vlan_default_pvid(struct net_bridge_vlan_group *vg, u16 vid)\nnet/bridge/br_vlan.c-1099-{\n--\nnet/bridge/br_vlan.c=1106=static void br_vlan_disable_default_pvid(struct net_bridge *br)\n--\nnet/bridge/br_vlan.c-1113-\t */\nnet/bridge/br_vlan.c:1114:\tif (vlan_default_pvid(br_vlan_group(br), pvid)) {\nnet/bridge/br_vlan.c-1115-\t\tif (!br_vlan_delete(br, pvid))\n--\nnet/bridge/br_vlan.c-1119-\tlist_for_each_entry(p, \u0026br-\u003eport_list, list) {\nnet/bridge/br_vlan.c:1120:\t\tif (vlan_default_pvid(nbp_vlan_group(p), pvid) \u0026\u0026\nnet/bridge/br_vlan.c-1121-\t\t    !nbp_vlan_delete(p, pvid))\n--\nnet/bridge/br_vlan.c=1128=int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,\n--\nnet/bridge/br_vlan.c-1131-\tconst struct net_bridge_vlan *pvent;\nnet/bridge/br_vlan.c:1132:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1133-\tstruct net_bridge_port *p;\n--\nnet/bridge/br_vlan.c-1152-\t */\nnet/bridge/br_vlan.c:1153:\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-1154-\tpvent = br_vlan_find(vg, pvid);\n--\nnet/bridge/br_vlan.c-1174-\t\t */\nnet/bridge/br_vlan.c:1175:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1176-\t\tif ((old_pvid \u0026\u0026\n--\nnet/bridge/br_vlan.c=1253=int br_vlan_init(struct net_bridge *br)\nnet/bridge/br_vlan.c-1254-{\nnet/bridge/br_vlan.c:1255:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1256-\tint ret = -ENOMEM;\n--\nnet/bridge/br_vlan.c=1283=int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c-1290-\t};\nnet/bridge/br_vlan.c:1291:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1292-\tint ret = -ENOMEM;\nnet/bridge/br_vlan.c-1293-\nnet/bridge/br_vlan.c:1294:\tvg = kzalloc_obj(struct net_bridge_vlan_group);\nnet/bridge/br_vlan.c-1295-\tif (!vg)\n--\nnet/bridge/br_vlan.c=1341=int nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags,\n--\nnet/bridge/br_vlan.c-1349-\t*changed = false;\nnet/bridge/br_vlan.c:1350:\tvlan = br_vlan_find(nbp_vlan_group(port), vid);\nnet/bridge/br_vlan.c-1351-\tif (vlan) {\n--\nnet/bridge/br_vlan.c=1386=int nbp_vlan_delete(struct net_bridge_port *port, u16 vid)\n--\nnet/bridge/br_vlan.c-1391-\nnet/bridge/br_vlan.c:1392:\tv = br_vlan_find(nbp_vlan_group(port), vid);\nnet/bridge/br_vlan.c-1393-\tif (!v)\n--\nnet/bridge/br_vlan.c=1402=void nbp_vlan_flush(struct net_bridge_port *port)\nnet/bridge/br_vlan.c-1403-{\nnet/bridge/br_vlan.c:1404:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1405-\n--\nnet/bridge/br_vlan.c-1407-\nnet/bridge/br_vlan.c:1408:\tvg = nbp_vlan_group(port);\nnet/bridge/br_vlan.c-1409-\t__vlan_flush(port-\u003ebr, port, vg);\n--\nnet/bridge/br_vlan.c-1411-\tsynchronize_net();\nnet/bridge/br_vlan.c:1412:\t__vlan_group_free(vg);\nnet/bridge/br_vlan.c-1413-}\n--\nnet/bridge/br_vlan.c=1442=int br_vlan_get_pvid(const struct net_device *dev, u16 *p_pvid)\nnet/bridge/br_vlan.c-1443-{\nnet/bridge/br_vlan.c:1444:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1445-\tstruct net_bridge_port *p;\n--\nnet/bridge/br_vlan.c-1449-\tif (p)\nnet/bridge/br_vlan.c:1450:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1451-\telse if (netif_is_bridge_master(dev))\nnet/bridge/br_vlan.c:1452:\t\tvg = br_vlan_group(netdev_priv(dev));\nnet/bridge/br_vlan.c-1453-\telse\n--\nnet/bridge/br_vlan.c=1461=int br_vlan_get_pvid_rcu(const struct net_device *dev, u16 *p_pvid)\nnet/bridge/br_vlan.c-1462-{\nnet/bridge/br_vlan.c:1463:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1464-\tstruct net_bridge_port *p;\n--\nnet/bridge/br_vlan.c-1467-\tif (p)\nnet/bridge/br_vlan.c:1468:\t\tvg = nbp_vlan_group_rcu(p);\nnet/bridge/br_vlan.c-1469-\telse if (netif_is_bridge_master(dev))\nnet/bridge/br_vlan.c:1470:\t\tvg = br_vlan_group_rcu(netdev_priv(dev));\nnet/bridge/br_vlan.c-1471-\telse\n--\nnet/bridge/br_vlan.c=1479=void br_vlan_fill_forward_path_pvid(struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-1482-{\nnet/bridge/br_vlan.c:1483:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1484-\tint idx = ctx-\u003enum_vlans - 1;\n--\nnet/bridge/br_vlan.c-1491-\nnet/bridge/br_vlan.c:1492:\tvg = br_vlan_group_rcu(br);\nnet/bridge/br_vlan.c-1493-\n--\nnet/bridge/br_vlan.c=1506=int br_vlan_fill_forward_path_mode(struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-1509-{\nnet/bridge/br_vlan.c:1510:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1511-\tstruct net_bridge_vlan *v;\n--\nnet/bridge/br_vlan.c-1515-\nnet/bridge/br_vlan.c:1516:\tvg = nbp_vlan_group_rcu(dst);\nnet/bridge/br_vlan.c-1517-\tv = br_vlan_find(vg, path-\u003ebridge.vlan_id);\n--\nnet/bridge/br_vlan.c=1534=int br_vlan_get_info(const struct net_device *dev, u16 vid,\n--\nnet/bridge/br_vlan.c-1536-{\nnet/bridge/br_vlan.c:1537:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1538-\tstruct net_bridge_vlan *v;\n--\nnet/bridge/br_vlan.c-1543-\tif (p)\nnet/bridge/br_vlan.c:1544:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1545-\telse if (netif_is_bridge_master(dev))\nnet/bridge/br_vlan.c:1546:\t\tvg = br_vlan_group(netdev_priv(dev));\nnet/bridge/br_vlan.c-1547-\telse\n--\nnet/bridge/br_vlan.c=1562=int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,\n--\nnet/bridge/br_vlan.c-1564-{\nnet/bridge/br_vlan.c:1565:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1566-\tstruct net_bridge_vlan *v;\n--\nnet/bridge/br_vlan.c-1570-\tif (p)\nnet/bridge/br_vlan.c:1571:\t\tvg = nbp_vlan_group_rcu(p);\nnet/bridge/br_vlan.c-1572-\telse if (netif_is_bridge_master(dev))\nnet/bridge/br_vlan.c:1573:\t\tvg = br_vlan_group_rcu(netdev_priv(dev));\nnet/bridge/br_vlan.c-1574-\telse\n--\nnet/bridge/br_vlan.c=1656=static void br_vlan_set_vlan_dev_state(const struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-1659-\tu16 vid = vlan_dev_priv(vlan_dev)-\u003evlan_id;\nnet/bridge/br_vlan.c:1660:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1661-\tstruct net_bridge_port *p;\n--\nnet/bridge/br_vlan.c-1669-\tlist_for_each_entry(p, \u0026br-\u003eport_list, list) {\nnet/bridge/br_vlan.c:1670:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1671-\t\tif (br_vlan_find(vg, vid) \u0026\u0026 br_vlan_is_dev_up(p-\u003edev)) {\n--\nnet/bridge/br_vlan.c=1683=static void br_vlan_set_all_vlan_dev_state(struct net_bridge_port *p)\nnet/bridge/br_vlan.c-1684-{\nnet/bridge/br_vlan.c:1685:\tstruct net_bridge_vlan_group *vg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1686-\tstruct net_bridge_vlan *vlan;\n--\nnet/bridge/br_vlan.c=1939=void br_vlan_notify(const struct net_bridge *br,\n--\nnet/bridge/br_vlan.c-1943-{\nnet/bridge/br_vlan.c:1944:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-1945-\tstruct net_bridge_vlan *v = NULL;\n--\nnet/bridge/br_vlan.c-1958-\t\tifindex = p-\u003edev-\u003eifindex;\nnet/bridge/br_vlan.c:1959:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-1960-\t\tnet = dev_net(p-\u003edev);\n--\nnet/bridge/br_vlan.c-1962-\t\tifindex = br-\u003edev-\u003eifindex;\nnet/bridge/br_vlan.c:1963:\t\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-1964-\t\tnet = dev_net(br-\u003edev);\n--\nnet/bridge/br_vlan.c=2021=static int br_vlan_dump_dev(const struct net_device *dev,\n--\nnet/bridge/br_vlan.c-2028-\tbool dump_stats = !!(dump_flags \u0026 BRIDGE_VLANDB_DUMPF_STATS);\nnet/bridge/br_vlan.c:2029:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-2030-\tint idx = 0, s_idx = cb-\u003eargs[1];\n--\nnet/bridge/br_vlan.c-2042-\t\tbr = netdev_priv(dev);\nnet/bridge/br_vlan.c:2043:\t\tvg = br_vlan_group_rcu(br);\nnet/bridge/br_vlan.c-2044-\t\tp = NULL;\n--\nnet/bridge/br_vlan.c-2052-\t\t\treturn -EINVAL;\nnet/bridge/br_vlan.c:2053:\t\tvg = nbp_vlan_group_rcu(p);\nnet/bridge/br_vlan.c-2054-\t\tbr = p-\u003ebr;\n--\nnet/bridge/br_vlan.c=2211=static int br_vlan_rtm_process_one(struct net_device *dev,\n--\nnet/bridge/br_vlan.c-2217-\tbool changed = false, skip_processing = false;\nnet/bridge/br_vlan.c:2218:\tstruct net_bridge_vlan_group *vg;\nnet/bridge/br_vlan.c-2219-\tstruct net_bridge_port *p = NULL;\n--\nnet/bridge/br_vlan.c-2224-\t\tbr = netdev_priv(dev);\nnet/bridge/br_vlan.c:2225:\t\tvg = br_vlan_group(br);\nnet/bridge/br_vlan.c-2226-\t} else {\n--\nnet/bridge/br_vlan.c-2230-\t\tbr = p-\u003ebr;\nnet/bridge/br_vlan.c:2231:\t\tvg = nbp_vlan_group(p);\nnet/bridge/br_vlan.c-2232-\t}\n"
}

43/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 40370
							output: 39
							thoughts: 21
						

Thoughts:
Here's my summary, as if I were thinking these thoughts:

**Diving into the VLAN Initialization**

Alright, time to dig into the VLAN initialization process. Specifically, I need to check out the `br_vlan_init` function in `br_vlan.c`. This is where the VLAN configuration gets started, so understanding this is critical for diagnosing any VLAN-related issues or optimizing the implementation. It's a key entry point, and knowing how it sets up the structures and handles initial states is fundamental. Let's see what's happening under the hood. I expect to find the core logic for setting up VLAN interfaces, initializing data structures related to VLAN tagging and untagging, and likely, registration with the bridge. I'm hoping to get a clear picture of how the VLAN module integrates with the broader bridge functionality.



44/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 1250,
  "LineCount": 35
}

Results:
{
  "Contents": "1250:\t\treturn err;\n1251:\t}\n1252:\t\n1253:\tint br_vlan_init(struct net_bridge *br)\n1254:\t{\n1255:\t\tstruct net_bridge_vlan_group *vg;\n1256:\t\tint ret = -ENOMEM;\n1257:\t\n1258:\t\tvg = kzalloc_obj(*vg);\n1259:\t\tif (!vg)\n1260:\t\t\tgoto out;\n1261:\t\tret = rhashtable_init(\u0026vg-\u003evlan_hash, \u0026br_vlan_rht_params);\n1262:\t\tif (ret)\n1263:\t\t\tgoto err_rhtbl;\n1264:\t\tret = vlan_tunnel_init(vg);\n1265:\t\tif (ret)\n1266:\t\t\tgoto err_tunnel_init;\n1267:\t\tINIT_LIST_HEAD(\u0026vg-\u003evlan_list);\n1268:\t\tbr-\u003evlan_proto = htons(ETH_P_8021Q);\n1269:\t\tbr-\u003edefault_pvid = 1;\n1270:\t\trcu_assign_pointer(br-\u003evlgrp, vg);\n1271:\t\n1272:\tout:\n1273:\t\treturn ret;\n1274:\t\n1275:\terr_tunnel_init:\n1276:\t\trhashtable_destroy(\u0026vg-\u003evlan_hash);\n1277:\terr_rhtbl:\n1278:\t\tkfree(vg);\n1279:\t\n1280:\t\tgoto out;\n1281:\t}\n1282:\t\n1283:\tint nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)\n1284:\t{\n"
}

45/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41105
							output: 32
							thoughts: 57
						

Thoughts:
**My Initial Thoughts on VLAN Allocation**

Okay, so `vg` gets its memory allocated using `kzalloc_obj(*vg)`. That's standard practice, initializing the memory to zero. This is important to note: because of this, `vg->pvid` starts out as NULL, which I'll keep in mind as I look at how things get set up later.

Now, I'm interested in how `struct net_bridge_vlan` itself is allocated and managed. Let's dig into that and see what we're working with here.



46/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "kzalloc.*net_bridge_vlan",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=1283=int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c-1293-\nnet/bridge/br_vlan.c:1294:\tvg = kzalloc_obj(struct net_bridge_vlan_group);\nnet/bridge/br_vlan.c-1295-\tif (!vg)\n"
}

47/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41449
							output: 42
							thoughts: 17
						

Thoughts:
Here's my thought process, as it stands:

**Locating the Allocation of `struct net_bridge_vlan`**

Okay, so I need to track down where the `struct net_bridge_vlan` is being allocated in the kernel code. That's a fundamental step in understanding its lifecycle and how it's used. This likely means tracing the execution path, or better yet, using a symbol lookup tool to point me directly to the allocation site. Given my expertise, I suspect this struct is central to VLAN bridging functionality. I'll probably encounter a call to something like `kmalloc`, `kzalloc`, `vmalloc` or maybe even some custom memory management within the bridging subsystem. The goal is pinpoint the exact location in the source code. From there, I can begin to identify the relationships with other data structures, and the context in which the structure is used.



48/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "alloc.*net_bridge_vlan|sizeof\\(struct net_bridge_vlan\\)",
  "PathPrefix": "net/bridge"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=1283=int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c-1293-\nnet/bridge/br_vlan.c:1294:\tvg = kzalloc_obj(struct net_bridge_vlan_group);\nnet/bridge/br_vlan.c-1295-\tif (!vg)\n"
}

49/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 41828
							output: 37
							thoughts: 17
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:

**Investigating VLAN Bridge Allocations**

Okay, so I need to find the allocations of `struct net_bridge_vlan *`. This is the starting point for understanding how VLANs are handled within the bridge context. I'm thinking this likely involves memory management, potentially in the core bridging code. I'll need to use search terms that are precise, yet encompass the possible allocation methods. Looking for the allocation function itself would be a good start. From there, I'll need to follow the trail to find where the pointer is then used.



50/3 2026/09/20 21:31 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "kzalloc\\(|kmalloc\\(",
  "PathPrefix": "net/bridge/br_vlan.c"
}

Results:
{
  "error": "no matches"
}

51/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42035
							output: 38
							thoughts: 32
						

Thoughts:
Here's my thought process, as if I were considering those functions:

**Investigating VLAN Allocation and Management in the Bridge**

Okay, I need to dive into how VLANs are handled within the bridge. My initial instinct is to zero in on the core VLAN functions. I'm thinking, let's start by looking at `br_vlan_alloc`. That seems like the logical entry point for, well, allocating a VLAN ID. Then, I should probably understand how a VLAN is actually *created* – so `br_vlan_create` is on the list. Finally, I'll need to figure out how to retrieve the "master" interface associated with a VLAN. The function that sounds like it handles that is `br_vlan_get_master`. I want to understand the interaction between these functions. What are the dependencies? How is the allocation tied to the creation and retrieval? Understanding these will provide the architecture I need to move forward.



52/3 2026/09/20 21:31 tool grepper
0m Args:
{
  "Expression": "br_vlan_get_master",
  "PathPrefix": "net/bridge/br_vlan.c"
}

Results:
{
  "Output": "net/bridge/br_vlan.c=175=static struct net_bridge_vlan *\nnet/bridge/br_vlan.c:176:br_vlan_get_master(struct net_bridge *br, u16 vid,\nnet/bridge/br_vlan.c-177-\t\t   struct netlink_ext_ack *extack)\n--\nnet/bridge/br_vlan.c=302=static int __vlan_add(struct net_bridge_vlan *v, u16 flags,\n--\nnet/bridge/br_vlan.c-346-\nnet/bridge/br_vlan.c:347:\t\tmasterv = br_vlan_get_master(br, v-\u003evid, extack);\nnet/bridge/br_vlan.c-348-\t\tif (!masterv) {\n"
}

53/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 42530
							output: 38
							thoughts: 26
						

Thoughts:
Okay, here's my interpretation of what's going through my mind as I approach this code snippet, tailored for a fellow expert:

**Diving into br_vlan.c: Lines 175-200**

Alright, time to crack open `net/bridge/br_vlan.c` and zero in on lines 175 to 200. This is usually where the magic happens, right? I'm expecting this section to deal with either VLAN configuration, VLAN tagging/untagging logic, or maybe even VLAN filtering within the bridging context. The file name screams VLAN handling, so I'm already anticipating some calls to `vlan_vid_add()`, `vlan_dev_set_egress_map()`, or related functions – the core VLAN manipulation stuff.

Given the line range, I'm guessing it's likely a relatively self-contained chunk – maybe part of an initialization function, a handler for a specific netlink message, or potentially a section within a larger loop that processes incoming packets or bridge events. I need to quickly scan the surrounding code to grasp the function's scope, arguments, and return values before diving deeper. I'm already mentally preparing to see if there are any obvious race conditions, use-after-free scenarios, or potential memory leaks. It's also important to consider: are there any quirks with the implementation that will influence how it works? Is it all standard or are there unique features?

I'm ready to find out if it's got any clever optimizations or, unfortunately, any legacy cruft that needs careful attention. Let's see what gems are hidden in those lines!



54/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 175,
  "LineCount": 35
}

Results:
{
  "Contents": " 175:\tstatic struct net_bridge_vlan *\n 176:\tbr_vlan_get_master(struct net_bridge *br, u16 vid,\n 177:\t\t\t   struct netlink_ext_ack *extack)\n 178:\t{\n 179:\t\tstruct net_bridge_vlan_group *vg;\n 180:\t\tstruct net_bridge_vlan *masterv;\n 181:\t\n 182:\t\tvg = br_vlan_group(br);\n 183:\t\tmasterv = br_vlan_find(vg, vid);\n 184:\t\tif (!masterv) {\n 185:\t\t\tbool changed;\n 186:\t\n 187:\t\t\t/* missing global ctx, create it now */\n 188:\t\t\tif (br_vlan_add(br, vid, 0, \u0026changed, extack))\n 189:\t\t\t\treturn NULL;\n 190:\t\t\tmasterv = br_vlan_find(vg, vid);\n 191:\t\t\tif (WARN_ON(!masterv))\n 192:\t\t\t\treturn NULL;\n 193:\t\t\trefcount_set(\u0026masterv-\u003erefcnt, 1);\n 194:\t\t\treturn masterv;\n 195:\t\t}\n 196:\t\trefcount_inc(\u0026masterv-\u003erefcnt);\n 197:\t\n 198:\t\treturn masterv;\n 199:\t}\n 200:\t\n 201:\tstatic void br_master_vlan_rcu_free(struct rcu_head *rcu)\n 202:\t{\n 203:\t\tstruct net_bridge_vlan *v;\n 204:\t\n 205:\t\tv = container_of(rcu, struct net_bridge_vlan, rcu);\n 206:\t\tWARN_ON(!br_vlan_is_master(v));\n 207:\t\tfree_percpu(v-\u003estats);\n 208:\t\tv-\u003estats = NULL;\n 209:\t\tkfree(v);\n"
}

55/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43490
							output: 38
							thoughts: 50
						

Thoughts:
Okay, here's my understanding, written as if it's my own thought process:

**Hunting Down VLAN Allocation Logic**

Alright, I need to understand how VLANs are being allocated within this code. The key is going to be tracing the allocation process, and I'm guessing it's handled somewhat centrally. So, I need to find the function responsible for that. Let's start by looking for something like `br_vlan_alloc` - that seems like a likely name, though it might be named something more specific or abstract. If that doesn't immediately jump out, I'll check `br_vlan_add` which is probably called in the allocation process at line 811. If it isn't `br_vlan_add`, then the function name `nbp_vlan_add` (line 1341) seems like a good alternative to consider. That should provide a good jumping-off point to understand the overall allocation strategy.



56/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 811,
  "LineCount": 45
}

Results:
{
  "Contents": " 811:\tint br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,\n 812:\t\t\tstruct netlink_ext_ack *extack)\n 813:\t{\n 814:\t\tstruct net_bridge_vlan_group *vg;\n 815:\t\tstruct net_bridge_vlan *vlan;\n 816:\t\tint ret;\n 817:\t\n 818:\t\tASSERT_RTNL();\n 819:\t\n 820:\t\t*changed = false;\n 821:\t\tvg = br_vlan_group(br);\n 822:\t\tvlan = br_vlan_find(vg, vid);\n 823:\t\tif (vlan)\n 824:\t\t\treturn br_vlan_add_existing(br, vg, vlan, flags, changed,\n 825:\t\t\t\t\t\t    extack);\n 826:\t\n 827:\t\tvlan = kzalloc_obj(*vlan);\n 828:\t\tif (!vlan)\n 829:\t\t\treturn -ENOMEM;\n 830:\t\n 831:\t\tvlan-\u003estats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);\n 832:\t\tif (!vlan-\u003estats) {\n 833:\t\t\tkfree(vlan);\n 834:\t\t\treturn -ENOMEM;\n 835:\t\t}\n 836:\t\tvlan-\u003evid = vid;\n 837:\t\tvlan-\u003eflags = flags | BRIDGE_VLAN_INFO_MASTER;\n 838:\t\tvlan-\u003eflags \u0026= ~BRIDGE_VLAN_INFO_PVID;\n 839:\t\tvlan-\u003ebr = br;\n 840:\t\tif (flags \u0026 BRIDGE_VLAN_INFO_BRENTRY)\n 841:\t\t\trefcount_set(\u0026vlan-\u003erefcnt, 1);\n 842:\t\tret = __vlan_add(vlan, flags, extack);\n 843:\t\tif (ret) {\n 844:\t\t\tfree_percpu(vlan-\u003estats);\n 845:\t\t\tkfree(vlan);\n 846:\t\t} else {\n 847:\t\t\t*changed = true;\n 848:\t\t}\n 849:\t\n 850:\t\treturn ret;\n 851:\t}\n 852:\t\n 853:\t/* Must be protected by RTNL.\n 854:\t * Must be called with vid in range from 1 to 4094 inclusive.\n 855:\t */\n"
}

57/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 44468
							output: 39
							thoughts: 22
						

Thoughts:
Here's my summary, as though I'm thinking through this code snippet:

**Memory Allocation and VLAN Addition**

Okay, so I see `kzalloc_obj(*vlan);`! That immediately tells me we're allocating memory for a VLAN object. `kzalloc` is the kernel's zeroed allocation function, which means this new VLAN object will be initialized to all zeros. A crucial step, of course, to avoid any unintended initial values and ensure a clean slate.  The `*vlan` part is interesting – it implies we're working with a pointer to a VLAN structure, and presumably, `kzalloc_obj` handles the size calculation and allocation based on the VLAN structure's definition. We're getting that space and setting up for the VLAN management tasks.

And then we call `nbp_vlan_add`. This is the function that actually *adds* this newly created VLAN to whatever internal data structure the system uses to track VLANs. This implies there's some kind of VLAN management system, perhaps a linked list, a hash table, or something more sophisticated, and this `nbp_vlan_add` routine is the mechanism to insert our new VLAN into that system. Without knowing the code details, I'm already imagining this `nbp_vlan_add` will likely be responsible for things like locking the data structure for thread safety, validating the VLAN parameters, and setting up the links or indexes to accurately manage it. We got the space, now we're putting it to work.



58/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 1340,
  "LineCount": 40
}

Results:
{
  "Contents": "1340:\t */\n1341:\tint nbp_vlan_add(struct net_bridge_port *port, u16 vid, u16 flags,\n1342:\t\t\t bool *changed, struct netlink_ext_ack *extack)\n1343:\t{\n1344:\t\tstruct net_bridge_vlan *vlan;\n1345:\t\tint ret;\n1346:\t\n1347:\t\tASSERT_RTNL();\n1348:\t\n1349:\t\t*changed = false;\n1350:\t\tvlan = br_vlan_find(nbp_vlan_group(port), vid);\n1351:\t\tif (vlan) {\n1352:\t\t\tbool would_change = __vlan_flags_would_change(vlan, flags);\n1353:\t\n1354:\t\t\tif (would_change) {\n1355:\t\t\t\t/* Pass the flags to the hardware bridge */\n1356:\t\t\t\tret = br_switchdev_port_vlan_add(port-\u003edev, vid, flags,\n1357:\t\t\t\t\t\t\t\t true, extack);\n1358:\t\t\t\tif (ret \u0026\u0026 ret != -EOPNOTSUPP)\n1359:\t\t\t\t\treturn ret;\n1360:\t\t\t}\n1361:\t\n1362:\t\t\t__vlan_flags_commit(vlan, flags);\n1363:\t\t\t*changed = would_change;\n1364:\t\n1365:\t\t\treturn 0;\n1366:\t\t}\n1367:\t\n1368:\t\tvlan = kzalloc_obj(*vlan);\n1369:\t\tif (!vlan)\n1370:\t\t\treturn -ENOMEM;\n1371:\t\n1372:\t\tvlan-\u003evid = vid;\n1373:\t\tvlan-\u003eport = port;\n1374:\t\tret = __vlan_add(vlan, flags, extack);\n1375:\t\tif (ret)\n1376:\t\t\tkfree(vlan);\n1377:\t\telse\n1378:\t\t\t*changed = true;\n1379:\t\n"
}

59/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 45416
							output: 38
							thoughts: 126
						

Thoughts:
**Analyzing VLAN Data Structure Initialization and Deallocation**

Okay, so I'm looking at how the `struct net_bridge_vlan` is handled. It looks like both the master and port VLAN instances are allocated using `kzalloc_obj(*vlan)`. That's good, because `kzalloc` is going to zero out all the fields. That's critical! Including the new `port_array` and `port_vlist` fields. That saves me some extra initialization steps, which is always welcome.

Then, I see in `__vlan_add` that `INIT_LIST_HEAD(&v->port_vlist);` is used specifically for the master VLAN. That seems right. Initializing that list head is expected.

Hmm, but what happens when a master VLAN is freed? Specifically, when `masterv` is deallocated? Does the corresponding `masterv->port_array` also get freed? It's easy to overlook these memory management details, so I need to make sure I'm not introducing a memory leak or double-free vulnerability. Let's see... the code should handle that, but it's important to be 100% sure. I'm going to look carefully at the `br_master_vlan_rcu_free` function; that's where the deallocation logic likely resides, and where the related cleanup would be performed.



60/3 2026/09/20 21:31 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 200,
  "LineCount": 25
}

Results:
{
  "Contents": " 200:\t\n 201:\tstatic void br_master_vlan_rcu_free(struct rcu_head *rcu)\n 202:\t{\n 203:\t\tstruct net_bridge_vlan *v;\n 204:\t\n 205:\t\tv = container_of(rcu, struct net_bridge_vlan, rcu);\n 206:\t\tWARN_ON(!br_vlan_is_master(v));\n 207:\t\tfree_percpu(v-\u003estats);\n 208:\t\tv-\u003estats = NULL;\n 209:\t\tkfree(v);\n 210:\t}\n 211:\t\n 212:\tstatic void br_vlan_put_master(struct net_bridge_vlan *masterv)\n 213:\t{\n 214:\t\tstruct net_bridge_vlan_group *vg;\n 215:\t\n 216:\t\tif (!br_vlan_is_master(masterv))\n 217:\t\t\treturn;\n 218:\t\n 219:\t\tvg = br_vlan_group(masterv-\u003ebr);\n 220:\t\tif (refcount_dec_and_test(\u0026masterv-\u003erefcnt)) {\n 221:\t\t\trhashtable_remove_fast(\u0026vg-\u003evlan_hash,\n 222:\t\t\t\t\t       \u0026masterv-\u003evnode, br_vlan_rht_params);\n 223:\t\t\t__vlan_del_list(masterv);\n 224:\t\t\tbr_multicast_toggle_one_vlan(masterv, false);\n"
}

61/3 2026/09/20 21:31 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 46302
							output: 39
							thoughts: 789
						

Thoughts:
**Analyzing VLAN Information Retrieval and Notification**

Okay, let's break this down. My initial concern revolves around the potential for a race condition or memory corruption related to VLAN information, specifically when `br_vlan_put_master` frees `masterv`. I need to understand when that `refcount_dec_and_test` evaluates to true. Right, it's triggered when there are no ports associated with the VLAN (because each port holds a reference) *and* the bridge entry is either unset or deleted. This seems to happen in `__vlan_del`, when the last port is removed. I see the code calls `br_vlan_rebuild_port_array` which, if the count is zero or below the `BR_VLAN_PORT_ARRAY_THRESHOLD`, allocates `NULL` to `masterv->port_array` and frees the old array with `kvfree_rcu`. So, `masterv->port_array` can become `NULL`. While that's likely a memory leak that KASAN/kmemleak would catch, it's not a KMSAN issue, as such.

Now, my attention shifts to how this might affect other functions like `br_vlan_get_info`, `br_vlan_get_info_rcu`, and `br_vlan_notify`. I need to ensure that the process of populating or sending `p_vinfo` to userspace handles the potential for `masterv->port_array` to be NULL correctly, and that there are no uninitialized variables being returned.

I'm looking at the diffs now, specifically the changes in how the pvid is checked. It's now checking `v == rcu_access_pointer(vg->pvid)`. I need to ensure this is safe for a read in the context of `rcu_access_pointer`.

Alright, I'm checking lines 1520-1590 in `net/bridge/br_vlan.c` to see if there are any uninitialized variables being used. I need to make sure that the `p_vinfo` structure is always fully initialized before being returned or sent to userspace.



62/3 2026/09/20 21:32 tool read-file
0m Args:
{
  "File": "net/bridge/br_vlan.c",
  "FirstLine": 1525,
  "LineCount": 65
}

Results:
{
  "Contents": "1525:\t\t\tpath-\u003ebridge.vlan_mode = DEV_PATH_BR_VLAN_KEEP;\n1526:\t\telse if (READ_ONCE(v-\u003epriv_flags) \u0026 BR_VLFLAG_TAGGING_BY_SWITCHDEV)\n1527:\t\t\tpath-\u003ebridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG_HW;\n1528:\t\telse\n1529:\t\t\tpath-\u003ebridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG;\n1530:\t\n1531:\t\treturn 0;\n1532:\t}\n1533:\t\n1534:\tint br_vlan_get_info(const struct net_device *dev, u16 vid,\n1535:\t\t\t     struct bridge_vlan_info *p_vinfo)\n1536:\t{\n1537:\t\tstruct net_bridge_vlan_group *vg;\n1538:\t\tstruct net_bridge_vlan *v;\n1539:\t\tstruct net_bridge_port *p;\n1540:\t\n1541:\t\tASSERT_RTNL();\n1542:\t\tp = br_port_get_check_rtnl(dev);\n1543:\t\tif (p)\n1544:\t\t\tvg = nbp_vlan_group(p);\n1545:\t\telse if (netif_is_bridge_master(dev))\n1546:\t\t\tvg = br_vlan_group(netdev_priv(dev));\n1547:\t\telse\n1548:\t\t\treturn -EINVAL;\n1549:\t\n1550:\t\tv = br_vlan_find(vg, vid);\n1551:\t\tif (!v)\n1552:\t\t\treturn -ENOENT;\n1553:\t\n1554:\t\tp_vinfo-\u003evid = vid;\n1555:\t\tp_vinfo-\u003eflags = v-\u003eflags;\n1556:\t\tif (v == rcu_access_pointer(vg-\u003epvid))\n1557:\t\t\tp_vinfo-\u003eflags |= BRIDGE_VLAN_INFO_PVID;\n1558:\t\treturn 0;\n1559:\t}\n1560:\tEXPORT_SYMBOL_GPL(br_vlan_get_info);\n1561:\t\n1562:\tint br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,\n1563:\t\t\t\t struct bridge_vlan_info *p_vinfo)\n1564:\t{\n1565:\t\tstruct net_bridge_vlan_group *vg;\n1566:\t\tstruct net_bridge_vlan *v;\n1567:\t\tstruct net_bridge_port *p;\n1568:\t\n1569:\t\tp = br_port_get_check_rcu(dev);\n1570:\t\tif (p)\n1571:\t\t\tvg = nbp_vlan_group_rcu(p);\n1572:\t\telse if (netif_is_bridge_master(dev))\n1573:\t\t\tvg = br_vlan_group_rcu(netdev_priv(dev));\n1574:\t\telse\n1575:\t\t\treturn -EINVAL;\n1576:\t\n1577:\t\tv = br_vlan_find(vg, vid);\n1578:\t\tif (!v)\n1579:\t\t\treturn -ENOENT;\n1580:\t\n1581:\t\tp_vinfo-\u003evid = vid;\n1582:\t\tp_vinfo-\u003eflags = READ_ONCE(v-\u003eflags);\n1583:\t\tif (v == rcu_access_pointer(vg-\u003epvid))\n1584:\t\t\tp_vinfo-\u003eflags |= BRIDGE_VLAN_INFO_PVID;\n1585:\t\treturn 0;\n1586:\t}\n1587:\tEXPORT_SYMBOL_GPL(br_vlan_get_info_rcu);\n1588:\t\n1589:\tstatic int br_vlan_is_bind_vlan_dev(const struct net_device *dev)\n"
}

63/3 2026/09/20 21:32 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 48676
							output: 37
							thoughts: 2268
						

Thoughts:
**Analysis of `br_fwd_dst` Usage in Bridge Forwarding**

Okay, so I've been tracing the usage of the `br_fwd_dst` struct in the bridge forwarding code, specifically to ensure that there are no potential leaks or unintended copies of the struct, especially in relation to the `struct bridge_vlan_info` I was investigating earlier. I started with a review of how `p_vinfo->vid` and `flags` are initialized, which hasn't changed, and there were no padding changes in `struct bridge_vlan_info`.

I'm focused on `br_fwd_dst`, defined as:

```c
struct br_fwd_dst {
	const struct net_bridge_port *port;
	struct net_bridge_vlan *vlan;
};
```

My concern is whether `br_fwd_dst` is ever copied or leaks due to some improper memory management. I've found it's a local stack struct in `br_forward.c`, so that's a good start, but I need to examine all the places it's created and used.

1.  In `should_deliver_vlan`, `should_deliver`, and `__br_forward`, the struct is *read* from to determine if a packet should be forwarded, and to forward the packet. No issues here. It just reads values from the struct.
2.  In `deliver_clone`: It's read and passed to another function.
3.  In `br_forward`, `br_fwd_dst` is initialized locally, and `fwd.port` and `fwd.vlan` are explicitly set. The `should_deliver` function's return value determines whether the struct is used. No memory management concerns there.
4.  In `maybe_deliver`, a `br_fwd_dst` is used to store and update a previous fwd entry. I need to keep an eye on how this is managed.
5.  In `br_flood`, a `br_fwd_dst prev = {};` is declared and initialized to zero. This is promising. If `prev->port` is null, the code jumps to `out` which assigns `*prev = *fwd`. So `prev` now contains what was in `fwd`. Subsequent calls to `maybe_deliver` use this.

    I checked how flood works when v is null or non-null. When null, the loop sets `fwd.port = p` and the compiler defaults vlan to null. When v is non-null, `dst.port` and `dst.vlan` are both initialized.
6.  `br_multicast_flood` uses a similar pattern, with zero-initialized `prev` and `fwd` structs.

So far, it looks like `br_fwd_dst` is managed safely as a local stack struct. No dynamic allocation or copying that could lead to leaks.

Now, I'm checking `br_input.c`. I'm verifying where the vlan comes from for the `br_flood` function in `br_handle_frame_finish`. Let me check `br_handle_frame_finish` in `net/bridge/br_input.c`!



64/3 2026/09/20 21:32 tool read-file
0m Args:
{
  "File": "net/bridge/br_input.c",
  "FirstLine": 100,
  "LineCount": 40
}

Results:
{
  "Contents": " 100:\t\t\tif (p-\u003estate == BR_STATE_DISABLED) {\n 101:\t\t\t\treason = SKB_DROP_REASON_BRIDGE_INGRESS_STP_STATE;\n 102:\t\t\t\tgoto drop;\n 103:\t\t\t}\n 104:\t\n 105:\t\t\tstate = p-\u003estate;\n 106:\t\t}\n 107:\t\n 108:\t\tbrmctx = \u0026p-\u003ebr-\u003emulticast_ctx;\n 109:\t\tpmctx = \u0026p-\u003emulticast_ctx;\n 110:\t\tif (!br_allowed_ingress(p-\u003ebr, nbp_vlan_group_rcu(p), skb, \u0026vid,\n 111:\t\t\t\t\t\u0026state, \u0026vlan))\n 112:\t\t\tgoto out;\n 113:\t\n 114:\t\tif (test_bit(BR_PORT_LOCKED_BIT, \u0026p-\u003eflags)) {\n 115:\t\t\tstruct net_bridge_fdb_entry *fdb_src =\n 116:\t\t\t\tbr_fdb_find_rcu(br, eth_hdr(skb)-\u003eh_source, vid);\n 117:\t\n 118:\t\t\tif (!fdb_src) {\n 119:\t\t\t\t/* FDB miss. Create locked FDB entry if MAB is enabled\n 120:\t\t\t\t * and drop the packet.\n 121:\t\t\t\t */\n 122:\t\t\t\tif (test_bit(BR_PORT_MAB_BIT, \u0026p-\u003eflags))\n 123:\t\t\t\t\tbr_fdb_update(br, p, eth_hdr(skb)-\u003eh_source,\n 124:\t\t\t\t\t\t      vid, BIT(BR_FDB_LOCKED));\n 125:\t\t\t\tgoto drop;\n 126:\t\t\t} else if (READ_ONCE(fdb_src-\u003edst) != p ||\n 127:\t\t\t\t   test_bit(BR_FDB_LOCAL, \u0026fdb_src-\u003eflags)) {\n 128:\t\t\t\t/* FDB mismatch. Drop the packet without roaming. */\n 129:\t\t\t\tgoto drop;\n 130:\t\t\t} else if (test_bit(BR_FDB_LOCKED, \u0026fdb_src-\u003eflags)) {\n 131:\t\t\t\t/* FDB match, but entry is locked. Refresh it and drop\n 132:\t\t\t\t * the packet.\n 133:\t\t\t\t */\n 134:\t\t\t\tbr_fdb_update(br, p, eth_hdr(skb)-\u003eh_source, vid,\n 135:\t\t\t\t\t      BIT(BR_FDB_LOCKED));\n 136:\t\t\t\tgoto drop;\n 137:\t\t\t}\n 138:\t\t}\n 139:\t\n"
}

65/3 2026/09/20 21:32 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 52387
							output: 418
							thoughts: 3287
						

Thoughts:
**Analysis of Bridge VLAN Optimization Patch**

Okay, let's break this down. We're looking at a patch that aims to optimize the bridge's VLAN handling, specifically in packet forwarding and VLAN tagging. My initial scan focused on `vlan` in `br_handle_frame_finish`, but that's already initialized correctly within `br_allowed_ingress`. The code initializes the `vlan` variable passed to `br_allowed_ingress` at line 110 (either to `NULL` or a valid pointer), so we're good there.

Now, the core of this patch seems to involve replacing the storage of the PVID (Port VLAN ID) and its associated state within the `net_bridge_vlan_group` with a direct pointer to the relevant `net_bridge_vlan` structure. The reasoning is it eliminates redundant lookups and simplifies state management. The code has moved from checking whether the PVID is set based on its ID to checking whether the pointer is valid. The code then performs the relevant actions, which seems to improve readability and code reuse.

I see they've removed the `vg` parameter from `br_mst_vlan_set_state` and `br_vlan_modify_state` because the `vg->pvid` is now a direct pointer to `net_bridge_vlan`, so we're updating its state directly. This makes sense; it simplifies the function signatures.

The code now caches the port-VLAN entries in an array to optimize flooding paths. In addition, the destination port and VLAN are bundled into a `struct br_fwd_dst`, which avoids redundant VLAN lookups. Let's make sure these structures are properly initialized. The important parts are `net_bridge_vlan_port_array`, `count`, and the `vlans[]` array. Everything is initialized correctly in the code as well.

The patch seems to introduce a new structure, `struct br_fwd_dst`, which is then passed down the forwarding path. Looking at its usage, it's always initialized, either by zeroing (`{}`) or with assignments to all fields. I've confirmed every instance is initialized before use. The array is allocated using `kvmalloc`, and the code makes sure that all of the entries in the array are populated via the list. Every item in the list is guaranteed to be within the bounds of the array.

My main concerns here are uninitialized memory reads, especially in structures sent to userspace, or in conditional logic where an uninitialized variable could lead to unexpected behavior. However, after carefully reviewing the code, it seems like there are no uninitialized variables or struct fields exposed, especially in any user space interactions. The new structures, `br_fwd_dst`, are completely zero-initialized, and the `net_bridge_vlan_port_array` initialization and access are sound. Furthermore, there is no change in the condition where `BRIDGE_VLAN_INFO_PVID` is set.

The code does not change any existing kernel structures which are passed to user space. It only modified `br_vlan_get_info` / `br_vlan_get_info_rcu` / `br_vlan_notify`, where `flags |= BRIDGE_VLAN_INFO_PVID` condition was changed from `vid == br_get_pvid(vg)` to `v == rcu_access_pointer(vg->pvid)`. No struct fields or padding are left uninitialized.

Since there are no uninitialized memory reads that are detectable only by KMSAN, my recommendation is that `NeedsKMSAN` should be set to `false`. All the changes appear to be optimizations and refactoring, and any potential issues (like array overflows, out-of-bounds access, or locking bugs) are already detectable by KASAN, LOCKDEP, or standard debugging tools.

So, overall, this patch seems safe from a KMSAN perspective.



66/3 2026/09/20 21:32 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch refactors and optimizes packet flooding and forwarding within the Linux bridge VLAN subsystem:\n1. Replaces the scalar `pvid` and `pvid_state` in `struct net_bridge_vlan_group` with an RCU-protected pointer to `struct net_bridge_vlan`.\n2. Introduces `struct net_bridge_vlan_port_array` dynamically allocated on the master VLAN entry to optimize flooding across VLAN member ports.\n3. Groups the destination port and VLAN into an internal stack structure `struct br_fwd_dst` to pass through `br_flood_*` and `__br_forward`.\n4. Updates neighbor suppression functions to accept `struct net_bridge_vlan *` directly.\n\nAssessment against KMSAN vs KASAN:\n- No uninitialized memory usage: All instances of `struct br_fwd_dst` on the stack are explicitly initialized (using designated/zero initializers or direct assignment to all members) before being passed or read.\n- Dynamic allocations: Bridge VLAN objects continue to be allocated via `kzalloc_obj`, and `net_bridge_vlan_port_array` has its `count` and `vlans` array strictly initialized under RTNL lock based on the count of elements in `masterv-\u003eport_vlist`.\n- No user-space info-leaks: The changes do not expose any uninitialized data, padding, or buffers to user space. `br_vlan_get_info` and `br_vlan_notify` continue to populate the same netlink attributes with properly checked values.\n- Any bugs that could arise here (e.g. RCU lifetime/dereferencing, pointer validity, array index bounds) are covered by KASAN and standard bug detectors.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch refactors and optimizes packet flooding and forwarding within the Linux bridge VLAN subsystem:\n1. Replaces the scalar `pvid` and `pvid_state` in `struct net_bridge_vlan_group` with an RCU-protected pointer to `struct net_bridge_vlan`.\n2. Introduces `struct net_bridge_vlan_port_array` dynamically allocated on the master VLAN entry to optimize flooding across VLAN member ports.\n3. Groups the destination port and VLAN into an internal stack structure `struct br_fwd_dst` to pass through `br_flood_*` and `__br_forward`.\n4. Updates neighbor suppression functions to accept `struct net_bridge_vlan *` directly.\n\nAssessment against KMSAN vs KASAN:\n- No uninitialized memory usage: All instances of `struct br_fwd_dst` on the stack are explicitly initialized (using designated/zero initializers or direct assignment to all members) before being passed or read.\n- Dynamic allocations: Bridge VLAN objects continue to be allocated via `kzalloc_obj`, and `net_bridge_vlan_port_array` has its `count` and `vlans` array strictly initialized under RTNL lock based on the count of elements in `masterv-\u003eport_vlist`.\n- No user-space info-leaks: The changes do not expose any uninitialized data, padding, or buffers to user space. `br_vlan_get_info` and `br_vlan_notify` continue to populate the same netlink attributes with properly checked values.\n- Any bugs that could arise here (e.g. RCU lifetime/dereferencing, pointer validity, array index bounds) are covered by KASAN and standard bug detectors.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)