[RFC net-next v2 2/3] bonding: build NS probe headers before inserting VLAN tags
From: Xiang Mei (Microsoft) <hidden>
Date: 2026-07-29 23:01:35
Also in:
lkml, stable
Subsystem:
bonding driver, networking drivers, the rest · Maintainers:
Jay Vosburgh, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
bond_ns_send() passes the skb from ndisc_ns_create() to
bond_handle_vlan() before any header exists: ndisc_ns_create() returns
only the ICMPv6 message, and the IPv6 and link-layer headers are pushed
later by ndisc_send_skb(). Tagging that bare message causes two
failures:
- Corruption: bond_handle_vlan() inserts tags at mac_len = ETH_HLEN,
expecting an Ethernet header. arp_create() provides one,
ndisc_ns_create() does not, so each tag lands 12 bytes into the ICMPv6
message, inside the target address. Every config with two or more
stacked VLANs emits a probe no peer can answer.
- Panic: each tag also eats the headroom reserved for the IPv6 header
(LL_RESERVED_SPACE(dev) + sizeof(struct ipv6hdr) = 56 for a plain
Ethernet slave, leaving 56 - 4 * (N - 1) for N VLANs). Six leave
36 < 40 and the ip6_nd_hdr() push underflows skb->head:
skbuff: skb_under_panic: text:ffffffff85eef44b len:84 put:40
head:ffff88801400b740 data:ffff88801400b73c
tail:0x50 end:0x180 dev:veth0
kernel BUG at net/core/skbuff.c:214!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
Workqueue: bond0 bond_arp_monitor
RIP: 0010:skb_panic+0x142/0x230
Call Trace:
skb_push (net/core/skbuff.c:224)
ndisc_send_skb (net/ipv6/ndisc.c:454 net/ipv6/ndisc.c:506)
bond_ns_send (drivers/net/bonding/bond_main.c:3255)
bond_ns_send_all (drivers/net/bonding/bond_main.c:3313)
bond_arp_monitor (drivers/net/bonding/bond_main.c:3458)
process_one_work (kernel/workqueue.c:3322)
worker_thread (kernel/workqueue.c:3405)
kthread (kernel/kthread.c:436)
Kernel panic - not syncing: Fatal exception
Build the probe in the order the stack uses: bond_ns_build_ip6hdr()
pushes the IPv6 header, the probe traverses NF_INET_LOCAL_OUT as a bare
IPv6 packet, then bond_ns_add_llhdr() pushes the link-layer header,
bond_handle_vlan() adds the tags, and it is transmitted on the slave
being tested. NF_INET_LOCAL_OUT must run before tagging so netfilter
sees the IPv6 packet ndisc_send_skb() feeds it rather than a half-built
Ethernet frame; ndisc_attach_dst() supplies the dst
ip6_route_me_harder() dereferences unconditionally.
The tags now land at a real Ethernet header, so the target address is
intact, and pushing the headers first lets skb_vlan_push() grow the
headroom itself, so no push underflows skb->head. nf_hook() returns 1
on NF_ACCEPT with the skb still owned by the caller; any other verdict
means netfilter consumed it.
Fixes: 4e24be018eb9 ("bonding: add new parameter ns_targets")
Cc: stable@vger.kernel.org
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <redacted>
---
drivers/net/bonding/bond_main.c | 106 +++++++++++++++++++++++++++++++-
1 file changed, 103 insertions(+), 3 deletions(-)
diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c
index 522eab060f9e..d5a7a7dd1eb2 100644
--- a/drivers/net/bonding/bond_main.c
+++ b/drivers/net/bonding/bond_main.c@@ -90,6 +90,8 @@ #include <net/tls.h> #endif #include <net/ip6_route.h> +#include <net/ip6_checksum.h> +#include <linux/netfilter.h> #include <net/netdev_lock.h> #include <net/xdp.h>
@@ -3232,13 +3234,75 @@ static int bond_arp_rcv(const struct sk_buff *skb, struct bonding *bond, } #if IS_ENABLED(CONFIG_IPV6) +static int bond_ns_xmit_finish(struct net *net, struct sock *sk, + struct sk_buff *skb) +{ + return dev_queue_xmit(skb); +} + +static void bond_ns_build_ip6hdr(struct sk_buff *skb, struct net_device *dev, + const struct in6_addr *saddr, + const struct in6_addr *daddr) +{ + struct icmp6hdr *icmp6h = icmp6_hdr(skb); + unsigned int len = skb->len; + struct inet6_dev *idev; + struct ipv6hdr *hdr; + unsigned int tclass; + + icmp6h->icmp6_cksum = csum_ipv6_magic(saddr, daddr, len, + IPPROTO_ICMPV6, + csum_partial(icmp6h, len, 0)); + + rcu_read_lock(); + idev = __in6_dev_get(dev); + tclass = idev ? READ_ONCE(idev->cnf.ndisc_tclass) : 0; + rcu_read_unlock(); + + skb_push(skb, sizeof(*hdr)); + skb_reset_network_header(skb); + hdr = ipv6_hdr(skb); + + ip6_flow_hdr(hdr, tclass, 0); + hdr->payload_len = htons(len); + hdr->nexthdr = IPPROTO_ICMPV6; + hdr->hop_limit = 255; /* RFC 4861, 7.1.1 */ + hdr->saddr = *saddr; + hdr->daddr = *daddr; +} + +/* Push the Ethernet header after the LOCAL_OUT hooks, as arp_create() and the + * IPv6 output path do. + */ +static int bond_ns_add_llhdr(struct sk_buff *skb, struct net_device *dev, + const struct in6_addr *daddr) +{ + unsigned char ha[MAX_ADDR_LEN]; + int err; + + err = ndisc_mc_map(daddr, ha, dev, 1); + if (err < 0) + return err; + + err = dev_hard_header(skb, dev, ETH_P_IPV6, ha, dev->dev_addr, + skb->len); + if (err < 0) + return err; + + skb_reset_mac_header(skb); + return 0; +} + static void bond_ns_send(struct slave *slave, const struct in6_addr *daddr, const struct in6_addr *saddr, struct bond_vlan_tag *tags) { struct net_device *bond_dev = slave->bond->dev; struct net_device *slave_dev = slave->dev; + struct inet6_dev *idev; struct in6_addr mcaddr; struct sk_buff *skb; + struct net *net; + int ret; slave_dbg(bond_dev, slave_dev, "NS on slave: dst %pI6c src %pI6c\n", daddr, saddr);
@@ -3250,10 +3314,46 @@ static void bond_ns_send(struct slave *slave, const struct in6_addr *daddr, } addrconf_addr_solict_mult(daddr, &mcaddr); - if (bond_handle_vlan(slave, tags, skb)) { - slave_update_last_tx(slave); - ndisc_send_skb(skb, &mcaddr, saddr); + + /* ip6_route_me_harder() in the LOCAL_OUT path dereferences skb_dst() */ + if (ndisc_attach_dst(skb, &mcaddr, saddr)) { + net_err_ratelimited("NS route allocation failed\n"); + kfree_skb(skb); + return; } + + bond_ns_build_ip6hdr(skb, slave_dev, saddr, &mcaddr); + + rcu_read_lock(); + net = dev_net_rcu(slave_dev); + idev = __in6_dev_get(slave_dev); + IP6_INC_STATS(net, idev, IPSTATS_MIB_OUTREQUESTS); + + /* nf_hook() returns 1 on NF_ACCEPT with @skb still ours; any other + * verdict means netfilter consumed it. + */ + ret = nf_hook(NFPROTO_IPV6, NF_INET_LOCAL_OUT, net, NULL, skb, NULL, + slave_dev, bond_ns_xmit_finish); + if (ret == 1) { + ICMP6MSGOUT_INC_STATS(net, idev, NDISC_NEIGHBOUR_SOLICITATION); + ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUTMSGS); + } + rcu_read_unlock(); + + if (ret != 1) + return; + + if (bond_ns_add_llhdr(skb, slave_dev, &mcaddr) < 0) { + net_err_ratelimited("failed to build NS link-layer header\n"); + kfree_skb(skb); + return; + } + + if (!bond_handle_vlan(slave, tags, skb)) + return; + + slave_update_last_tx(slave); + dev_queue_xmit(skb); } static void bond_ns_send_all(struct bonding *bond, struct slave *slave)
--
2.43.0