]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
team: fix header_ops type confusion with non-Ethernet ports
authorJiayuan Chen <jiayuan.chen@shopee.com>
Fri, 20 Mar 2026 07:21:26 +0000 (15:21 +0800)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 24 Mar 2026 10:26:32 +0000 (11:26 +0100)
Similar to commit 950803f72547 ("bonding: fix type confusion in
bond_setup_by_slave()") team has the same class of header_ops type
confusion.

For non-Ethernet ports, team_setup_by_port() copies port_dev->header_ops
directly. When the team device later calls dev_hard_header() or
dev_parse_header(), these callbacks can run with the team net_device
instead of the real lower device, so netdev_priv(dev) is interpreted as
the wrong private type and can crash.

The syzbot report shows a crash in bond_header_create(), but the root
cause is in team: the topology is gre -> bond -> team, and team calls
the inherited header_ops with its own net_device instead of the lower
device, so bond_header_create() receives a team device and interprets
netdev_priv() as bonding private data, causing a type confusion crash.

Fix this by introducing team header_ops wrappers for create/parse,
selecting a team port under RCU, and calling the lower device callbacks
with port->dev, so each callback always sees the correct net_device
context.

Also pass the selected lower device to the lower parse callback, so
recursion is bounded in stacked non-Ethernet topologies and parse
callbacks always run with the correct device context.

Fixes: 1d76efe1577b ("team: add support for non-ethernet devices")
Reported-by: syzbot+3d8bc31c45e11450f24c@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/69b46af7.050a0220.36eb34.000e.GAE@google.com/T/
Cc: Jiayuan Chen <jiayuan.chen@linux.dev>
Signed-off-by: Jiayuan Chen <jiayuan.chen@shopee.com>
Link: https://patch.msgid.link/20260320072139.134249-2-jiayuan.chen@linux.dev
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/net/team/team_core.c

index b7282f5c96328a5643baa6f871cb8bee8624f2bb..120aeb539d9f2ac7351b8812d74f825a4d61f7ee 100644 (file)
@@ -2058,6 +2058,68 @@ static const struct ethtool_ops team_ethtool_ops = {
  * rt netlink interface
  ***********************/
 
+/* For tx path we need a linkup && enabled port and for parse any port
+ * suffices.
+ */
+static struct team_port *team_header_port_get_rcu(struct team *team,
+                                                 bool txable)
+{
+       struct team_port *port;
+
+       list_for_each_entry_rcu(port, &team->port_list, list) {
+               if (!txable || team_port_txable(port))
+                       return port;
+       }
+
+       return NULL;
+}
+
+static int team_header_create(struct sk_buff *skb, struct net_device *team_dev,
+                             unsigned short type, const void *daddr,
+                             const void *saddr, unsigned int len)
+{
+       struct team *team = netdev_priv(team_dev);
+       const struct header_ops *port_ops;
+       struct team_port *port;
+       int ret = 0;
+
+       rcu_read_lock();
+       port = team_header_port_get_rcu(team, true);
+       if (port) {
+               port_ops = READ_ONCE(port->dev->header_ops);
+               if (port_ops && port_ops->create)
+                       ret = port_ops->create(skb, port->dev,
+                                              type, daddr, saddr, len);
+       }
+       rcu_read_unlock();
+       return ret;
+}
+
+static int team_header_parse(const struct sk_buff *skb,
+                            const struct net_device *team_dev,
+                            unsigned char *haddr)
+{
+       struct team *team = netdev_priv(team_dev);
+       const struct header_ops *port_ops;
+       struct team_port *port;
+       int ret = 0;
+
+       rcu_read_lock();
+       port = team_header_port_get_rcu(team, false);
+       if (port) {
+               port_ops = READ_ONCE(port->dev->header_ops);
+               if (port_ops && port_ops->parse)
+                       ret = port_ops->parse(skb, port->dev, haddr);
+       }
+       rcu_read_unlock();
+       return ret;
+}
+
+static const struct header_ops team_header_ops = {
+       .create         = team_header_create,
+       .parse          = team_header_parse,
+};
+
 static void team_setup_by_port(struct net_device *dev,
                               struct net_device *port_dev)
 {
@@ -2066,7 +2128,8 @@ static void team_setup_by_port(struct net_device *dev,
        if (port_dev->type == ARPHRD_ETHER)
                dev->header_ops = team->header_ops_cache;
        else
-               dev->header_ops = port_dev->header_ops;
+               dev->header_ops = port_dev->header_ops ?
+                                 &team_header_ops : NULL;
        dev->type = port_dev->type;
        dev->hard_header_len = port_dev->hard_header_len;
        dev->needed_headroom = port_dev->needed_headroom;