Skip to contentSkip to conversation

net: nsh: handle nested NSH headers during GSO

PATCH In review Ren Wei 4 participants 4 messages Apr 20 – Apr 30 1 message not in the archive

Subsystems (2)
lore
Share & tools
lore

A message that is not in the archive (sent off-list or not yet received): [email protected]

Ren Wei

author
#1 lore raw

PATCH 1/1 net: nsh: handle nested NSH headers during GSO

From: Ruide Cao <[email protected]>

nsh_gso_segment() currently redispatches the inner payload through skb_mac_gso_segment() after stripping one NSH header. For nested NSH payloads, including Ethernet-encapsulated NSH payloads, this can cause repeated re-entry into nsh_gso_segment().

The existing validation added by commit af50e4ba34f4 ("nsh: fix infinite loop") only covers invalid header lengths and does not address repeated self-dispatch across nested NSH payload chains.

Handle nested NSH headers iteratively in a single nsh_gso_segment() invocation. Unwrap consecutive NSH headers until the first non-NSH payload is reached, including the case where the next redispatch target is reached through ETH_P_TEB, segment that payload once, and then restore the full outer encapsulation on each output segment. Also route validation failures through the same unwind path so the skb state is restored after any partial unwrap.

This keeps nested NSH GSO handling correct while avoiding recursive redispatch.

---
 net/nsh/nsh.c | 75 ++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 54 insertions(+), 21 deletions(-)
net/nsh/nsh.c +54 -21
index bfb7758063f3..89c240c2ed7c 100644
@@ -77,7 +77,7 @@ EXPORT_SYMBOL_GPL(nsh_pop);
 static struct sk_buff *nsh_gso_segment(struct sk_buff *skb,
 				       netdev_features_t features)
 {
-	unsigned int outer_hlen, mac_len, nsh_len;
+	unsigned int outer_hlen, mac_len, nsh_len, total_pull_len = 0;
 	struct sk_buff *segs = ERR_PTR(-EINVAL);
 	u16 mac_offset = skb->mac_header;
 	__be16 outer_proto, proto;
@@ -88,40 +88,73 @@ static struct sk_buff *nsh_gso_segment(struct sk_buff *skb,
 	outer_hlen = skb_mac_header_len(skb);
 	mac_len = skb->mac_len;
 
-	if (unlikely(!pskb_may_pull(skb, NSH_BASE_HDR_LEN)))
-		goto out;
-	nsh_len = nsh_hdr_len(nsh_hdr(skb));
-	if (nsh_len < NSH_BASE_HDR_LEN)
-		goto out;
-	if (unlikely(!pskb_may_pull(skb, nsh_len)))
-		goto out;
+	while (true) {
+		if (unlikely(!pskb_may_pull(skb, NSH_BASE_HDR_LEN)))
+			goto err;
+		nsh_len = nsh_hdr_len(nsh_hdr(skb));
+		if (nsh_len < NSH_BASE_HDR_LEN)
+			goto err;
+		if (unlikely(!pskb_may_pull(skb, nsh_len)))
+			goto err;
 
-	proto = tun_p_to_eth_p(nsh_hdr(skb)->np);
-	if (!proto)
-		goto out;
+		proto = tun_p_to_eth_p(nsh_hdr(skb)->np);
+		if (!proto)
+			goto err;
 
-	__skb_pull(skb, nsh_len);
+		__skb_pull(skb, nsh_len);
+		total_pull_len += nsh_len;
 
-	skb_reset_mac_header(skb);
-	skb->mac_len = proto == htons(ETH_P_TEB) ? ETH_HLEN : 0;
-	skb->protocol = proto;
+		skb_reset_mac_header(skb);
+		skb->mac_len = proto == htons(ETH_P_TEB) ? ETH_HLEN : 0;
+		skb->protocol = proto;
+
+		/* Keep unwrapping any payload that would redispatch back into
+		 * nsh_gso_segment(), including Ethernet-encapsulated NSH.
+		 */
+		if (proto == htons(ETH_P_NSH))
+			continue;
+
+		if (proto == htons(ETH_P_TEB)) {
+			int depth = skb->mac_len;
+
+			proto = skb_network_protocol(skb, &depth);
+			if (!proto)
+				goto err;
+
+			if (proto == htons(ETH_P_NSH)) {
+				__skb_pull(skb, depth);
+				total_pull_len += depth;
+
+				skb_reset_mac_header(skb);
+				skb->mac_len = 0;
+				skb->protocol = proto;
+				continue;
+			}
+		}
+
+		break;
+	}
 
 	features &= NETIF_F_SG;
 	segs = skb_mac_gso_segment(skb, features);
-	if (IS_ERR_OR_NULL(segs)) {
-		skb_gso_error_unwind(skb, htons(ETH_P_NSH), nsh_len,
-				     mac_offset, mac_len);
-		goto out;
-	}
+	if (IS_ERR_OR_NULL(segs))
+		goto err;
 
 	for (skb = segs; skb; skb = skb->next) {
 		skb->protocol = outer_proto;
-		__skb_push(skb, nsh_len + outer_hlen);
+		__skb_push(skb, total_pull_len + outer_hlen);
 		skb_reset_mac_header(skb);
 		skb_set_network_header(skb, outer_hlen);
 		skb->mac_len = mac_len;
 	}
 
+	goto out;
+
+err:
+	if (total_pull_len)
+		skb_gso_error_unwind(skb, outer_proto, total_pull_len,
+				     mac_offset, mac_len);
+
 out:
 	return segs;
 }
Signature · 1 line
2.34.1
syzbot Tested-by [email protected] on v1 1/1
#2 lore raw
syzbot ci has tested the following series

syzbot ci has tested the following series

[v1] net: nsh: handle nested NSH headers during GSO
https://lore.kernel.org/all/6112cce99b4e3571444a616d0fb19e91e2fcca72.1776597598.git.caoruide123@gmail.com
* [PATCH net 1/1] net: nsh: handle nested NSH headers during GSO

and found the following issue:
WARNING in nsh_gso_segment

Full report is available here:
https://ci.syzbot.org/series/13f77bac-014d-4059-9187-fbdbcb6a6540

***

WARNING in nsh_gso_segment

tree:      net
URL:       https://kernel.googlesource.com/pub/scm/linux/kernel/git/netdev/net.git
base:      0cf004ffb61cd32d140531c3a84afe975f9fc7ea
arch:      amd64
compiler:  Debian clang version 21.1.8 (++20251221033036+2078da43e25a-1~exp1~20251221153213.50), Debian LLD 21.1.8
config:    https://ci.syzbot.org/builds/193058cd-cd88-4bb0-a6e6-b43b6179fcd9/config
syz repro: https://ci.syzbot.org/findings/446db72d-a630-471c-b369-2ef1f7b3b6ed/syz_repro
------------[ cut here ]------------
offset != (typeof(skb->mac_header))offset
WARNING: ./include/linux/skbuff.h:3173 at skb_reset_mac_header include/linux/skbuff.h:3173 [inline], CPU#1: syz.1.20/5978
WARNING: ./include/linux/skbuff.h:3173 at nsh_gso_segment+0x833/0x12d0 net/nsh/nsh.c:107, CPU#1: syz.1.20/5978
Modules linked in:
CPU: 1 UID: 0 PID: 5978 Comm: syz.1.20 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:skb_reset_mac_header include/linux/skbuff.h:3173 [inline]
RIP: 0010:nsh_gso_segment+0x833/0x12d0 net/nsh/nsh.c:107
Code: 2f 08 00 00 48 8b 7c 24 60 44 89 f6 e8 46 83 e4 fd 48 85 c0 0f 84 20 08 00 00 e8 48 af 31 f6 e9 ab fb ff ff e8 3e af 31 f6 90 <0f> 0b 90 e9 43 fd ff ff e8 30 af 31 f6 90 0f 0b 90 e9 39 fe ff ff
RSP: 0018:ffffc90004847220 EFLAGS: 00010293
RAX: ffffffff8b93aeb2 RBX: ffff88817362a6d8 RCX: ffff88810f088000
RDX: 0000000000000000 RSI: 0000000000010040 RDI: 0000000000010000
RBP: ffffc90004847390 R08: ffff88810f088000 R09: 0000000000000005
R10: 0000000000000005 R11: 0000000000000000 R12: 0000000000000000
R13: 0000000000010040 R14: dffffc0000000000 R15: 0000000000000074
FS:  00007f28e9ed36c0(0000) GS:ffff8882a9245000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000200000010000 CR3: 0000000170f06000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 skb_mac_gso_segment+0x31c/0x690 net/core/gso.c:53
 __skb_gso_segment+0x376/0x540 net/core/gso.c:124
 skb_gso_segment include/net/gso.h:83 [inline]
 validate_xmit_skb+0xa21/0x14a0 net/core/dev.c:4039
 validate_xmit_skb_list+0x84/0x120 net/core/dev.c:4089
 sch_direct_xmit+0xdf/0x4c0 net/sched/sch_generic.c:357
 __dev_xmit_skb net/core/dev.c:4209 [inline]
 __dev_queue_xmit+0x180f/0x3950 net/core/dev.c:4831
 packet_snd net/packet/af_packet.c:3077 [inline]
 packet_sendmsg+0x3ebc/0x50f0 net/packet/af_packet.c:3109
 sock_sendmsg_nosec net/socket.c:787 [inline]
 __sock_sendmsg net/socket.c:802 [inline]
 __sys_sendto+0x672/0x710 net/socket.c:2265
 __do_sys_sendto net/socket.c:2272 [inline]
 __se_sys_sendto net/socket.c:2268 [inline]
 __x64_sys_sendto+0xde/0x100 net/socket.c:2268
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x15f/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f28e8f9c819
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f28e9ed3028 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
RAX: ffffffffffffffda RBX: 00007f28e9216090 RCX: 00007f28e8f9c819
RDX: 00000000000100a6 RSI: 0000200000000180 RDI: 0000000000000005
RBP: 00007f28e9032c91 R08: 0000200000000140 R09: 0000000000000014
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f28e9216128 R14: 00007f28e9216090 R15: 00007ffd50249d78
 </TASK>

***

If these findings have caused you to resend the series or submit a separate fix, please add the following tag to your commit message:

---
This report is generated by a bot. It may contain errors. syzbot ci engineers can be reached at [email protected].

To test a patch for this bug, please reply with #syz test (should be on a separate line).

The patch should be attached to the email.
Note: arguments like custom git repos and branches are not supported.

#3 lore raw
In reply to Ren Wei
Quoted 5 lines · Ren Wei

Handle nested NSH headers iteratively in a single nsh_gso_segment() invocation. Unwrap consecutive NSH headers until the first non-NSH payload is reached, including the case where the next redispatch target is reached through ETH_P_TEB, segment that payload once, and then restore the full outer encapsulation on each output segment.

This looks fragile. If there's ever another protocol with similar logic added, we'll be in the same situation. (And obviously, unrolling the recursion for any combination of protocols doesn't scale well.)

What about using a mechanism similar to dev_xmit_recursion to limit the depth and returning EINVAL if we exceed the limit? I think it's fine to drop the packet in this pathological case.

We might even just use dev_xmit_recursion directly, since it can be argued that such nested NSH headers are in fact nested tunnels.

Jiri

#4 lore raw
In reply to Ren Wei
kernel test robot noticed the following build errors:

Hi Ren,

kernel test robot noticed the following build errors:

[auto build test ERROR on net/main]

url:    https://github.com/intel-lab-lkp/linux/commits/Ren-Wei/net-nsh-handle-nested-NSH-headers-during-GSO/20260423-084001
base:   net/main
patch link:    https://lore.kernel.org/r/6112cce99b4e3571444a616d0fb19e91e2fcca72.1776597598.git.caoruide123%40gmail.com
patch subject: [PATCH net 1/1] net: nsh: handle nested NSH headers during GSO
config: i386-randconfig-003-20260430 (https://download.01.org/0day-ci/archive/20260430/[email protected]/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260430/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>, old ones prefixed by <<):

>> ERROR: modpost: "skb_network_protocol" [net/nsh/nsh.ko] undefined!
Signature · 2 lines
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki