Thread (4 messages) flat view 4 messages, 1 author, 9d ago

[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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help