Merge branch 'team-fix-header_ops-type-confusion-and-add-selftest'

Jiayuan Chen says:

====================
team: fix header_ops type confusion and add selftest

Hi,

This patch series fixes a panic reported by syzkaller in the team/bond/gre
stacked non-Ethernet configuration:
https://syzkaller.appspot.com/bug?extid=3d8bc31c45e11450f24c

The first patch fixes the header_ops type confusion / parse recursion
context issue in team. The second patch adds a selftest to reproduce the
reported scenario and prevent regressions in the future.

v1: https://lore.kernel.org/netdev/20260314062306.212765-1-jiayuan.chen@linux.dev/
v2: https://lore.kernel.org/netdev/20260317124606.157035-1-jiayuan.chen@linux.dev/
====================

Link: https://patch.msgid.link/20260320072139.134249-1-jiayuan.chen@linux.dev
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-03-24 11:26:34 +01:00
commit 25f5463c91
4 changed files with 108 additions and 1 deletions

View 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;

View File

@ -3,6 +3,7 @@
TEST_PROGS := \
dev_addr_lists.sh \
non_ether_header_ops.sh \
options.sh \
propagation.sh \
refleak.sh \

View File

@ -1,7 +1,9 @@
CONFIG_BONDING=y
CONFIG_DUMMY=y
CONFIG_IPV6=y
CONFIG_MACVLAN=y
CONFIG_NETDEVSIM=m
CONFIG_NET_IPGRE=y
CONFIG_NET_TEAM=y
CONFIG_NET_TEAM_MODE_ACTIVEBACKUP=y
CONFIG_NET_TEAM_MODE_LOADBALANCE=y

View File

@ -0,0 +1,41 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# shellcheck disable=SC2154
#
# Reproduce the non-Ethernet header_ops confusion scenario with:
# g0 (gre) -> b0 (bond) -> t0 (team)
#
# Before the fix, direct header_ops inheritance in this stack could call
# callbacks with the wrong net_device context and crash.
lib_dir=$(dirname "$0")
source "$lib_dir"/../../../net/lib.sh
trap cleanup_all_ns EXIT
setup_ns ns1
ip -n "$ns1" link add d0 type dummy
ip -n "$ns1" addr add 10.10.10.1/24 dev d0
ip -n "$ns1" link set d0 up
ip -n "$ns1" link add g0 type gre local 10.10.10.1
ip -n "$ns1" link add b0 type bond mode active-backup
ip -n "$ns1" link add t0 type team
ip -n "$ns1" link set g0 master b0
ip -n "$ns1" link set b0 master t0
ip -n "$ns1" link set g0 up
ip -n "$ns1" link set b0 up
ip -n "$ns1" link set t0 up
# IPv6 address assignment triggers MLD join reports that call
# dev_hard_header() on t0, exercising the inherited header_ops path.
ip -n "$ns1" -6 addr add 2001:db8:1::1/64 dev t0 nodad
for i in $(seq 1 20); do
ip netns exec "$ns1" ping -6 -I t0 ff02::1 -c1 -W1 &>/dev/null || true
done
echo "PASS: non-Ethernet header_ops stacking did not crash"
exit "$EXIT_STATUS"