FAILED: patch "[PATCH] net: gso: limit recursive IP-in-IP segmentation" failed to apply to 6.12-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 006c8b834aed8b02bd8fb9fb05e9e7ead37d1763
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026100305-stillness-plexiglas-1e85@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 006c8b834aed8b02bd8fb9fb05e9e7ead37d1763 Mon Sep 17 00:00:00 2001
From: Zihan Xi <[email protected]>
Date: Thu, 24 Sep 2026 05:15:21 +0000
Subject: [PATCH] net: gso: limit recursive IP-in-IP segmentation
IP-in-IP GSO can re-enter inet_gso_segment() or ipv6_gso_segment() for each nested IP header. encap_level tracks header bytes, not callback depth, so a deep chain can exhaust the kernel stack. Making inet_gso_segment() stackable introduced unbounded IPv4 nesting; IPIP GSO/TSO later made the path reachable. The IPv6 stackable path was introduced separately and uses the same guard.
Count IPv4 and IPv6 GSO handler entries in skb_gso_cb, initialized once per top-level GSO operation and preserved across GRE/UDP context changes. Use the existing IP_TUNNEL_RECURSION_LIMIT for both handlers. The first five entries pass, and the sixth returns -EINVAL before dispatching another GSO callback.
include/net/gso.h +8 -0
@@ -19,10 +19,18 @@ struct skb_gso_cb {int encap_level;__wsum csum;__u16 csum_start; +/* Number of IPv4/IPv6 GSO handler entries for this packet. */ +u8 recursion_counter; };#define SKB_GSO_CB_OFFSET 32#define SKB_GSO_CB (skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) +static inline bool gso_recursion_inc_test (struct sk_buff *skb, +unsigned int limit) +{ +return ++SKB_GSO_CB (skb)->recursion_counter > limit; +} +static inline int skb_tnl_header_len (const struct sk_buff *inner_skb) { return (skb_mac_header(inner_skb) - inner_skb->head) -
net/core/gso.c +1 -0
@@ -117,6 +117,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,SKB_GSO_CB (skb)->mac_offset =skb_headroom (skb);SKB_GSO_CB (skb)->encap_level =0 ; +SKB_GSO_CB (skb)->recursion_counter =0 ;skb_reset_mac_header (skb);skb_reset_mac_len (skb);
net/ipv4/af_inet.c +3 -0
@@ -1375,6 +1375,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,int id;skb_reset_network_header (skb); +if (unlikely (gso_recursion_inc_test (skb, +IP_TUNNEL_RECURSION_LIMIT ))) + goto out; nhoff =skb_network_header (skb) -skb_mac_header (skb);if (unlikely (!pskb_may_pull (skb,sizeof (*iph)))) goto out;
net/ipv6/ip6_offload.c +4 -0
@@ -17,6 +17,7 @@#include <net/udp.h> #include <net/gro.h> #include <net/gso.h> +#include <net/ip_tunnels.h> #include "ip6_offload.h" #include "tcpv6_offload.c"
@@ -104,6 +105,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,bool gso_partial;skb_reset_network_header (skb); +if (unlikely (gso_recursion_inc_test (skb, +IP_TUNNEL_RECURSION_LIMIT ))) + goto out; nhoff =skb_network_header (skb) -skb_mac_header (skb);if (unlikely (!pskb_may_pull (skb,sizeof (*ipv6h)))) goto out;
PATCH 1/1 net: gso: limit recursive IP-in-IP segmentation
From: Zihan Xi <[email protected]>
[ Upstream commit 006c8b834aed8b02bd8fb9fb05e9e7ead37d1763 ]
IP-in-IP GSO can re-enter inet_gso_segment() or ipv6_gso_segment() for each nested IP header. encap_level tracks header bytes, not callback depth, so a deep chain can exhaust the kernel stack. Making inet_gso_segment() stackable introduced unbounded IPv4 nesting; IPIP GSO/TSO later made the path reachable. The IPv6 stackable path was introduced separately and uses the same guard.
Count IPv4 and IPv6 GSO handler entries in skb_gso_cb, initialized once per top-level GSO operation and preserved across GRE/UDP context changes. Use the existing IP_TUNNEL_RECURSION_LIMIT for both handlers. The first five entries pass, and the sixth returns -EINVAL before dispatching another GSO callback.
[ Ting-Han: ipv6_gso_segment() still calls ipv6_hopopt_jumbo_remove()
right after skb_reset_network_header() in this version; keep the new recursion check directly after skb_reset_network_header() as upstream does, before the jumbo removal ]
---
Tested on top of v6.12.114. The patch also applies cleanly on top of the current 6.12 queue (stable-queue fa04d3db75).
x86_64 defconfig + kvm_guest.config with KASAN, PROVE_LOCKING, DEBUG_ATOMIC_SLEEP, DEBUG_LIST and UBSAN, built with and without the patch and booted in QEMU/KVM. The guest sends 32 MiB of TCP over a veth pair, over one ipip tunnel, over one ip6ip6 tunnel and over ipip in ipip, with tx-ipxip4/6-segmentation turned off on the veth pair so that the tunnel GSO packets are segmented in software. With and without the patch all data arrives intact, there are no drops or kernel warnings, and kprobes show two inet_gso_segment() or ipv6_gso_segment() calls per __skb_gso_segment() call in the tunnel cases, so these packets reach a count of 2 in the new check. No new W=1 or sparse warnings in the changed .c files.
Not tested: the deep nesting reproducer from the upstream report, so the -EINVAL path itself was not exercised.
Tool use: this backport was prepared with an LLM coding assistant (Claude Code with Claude Opus 5.5), asked to backport 006c8b834aed to the stable trees it failed to apply to and to test it. It resolved the conflicts, wrote the note in the changelog and this text, wrote the test scripts and the guest init, and ran the builds and QEMU tests above. I reviewed the patch and the test results before sending.
include/net/gso.h | 8 ++++++++ net/core/gso.c | 1 + net/ipv4/af_inet.c | 3 +++ net/ipv6/ip6_offload.c | 4 ++++ 4 files changed, 16 insertions(+)
include/net/gso.h +8 -0
@@ -19,10 +19,18 @@ struct skb_gso_cb {int encap_level;__wsum csum;__u16 csum_start; +/* Number of IPv4/IPv6 GSO handler entries for this packet. */ +u8 recursion_counter; };#define SKB_GSO_CB_OFFSET 32#define SKB_GSO_CB (skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) +static inline bool gso_recursion_inc_test (struct sk_buff *skb, +unsigned int limit) +{ +return ++SKB_GSO_CB (skb)->recursion_counter > limit; +} +static inline int skb_tnl_header_len (const struct sk_buff *inner_skb) { return (skb_mac_header(inner_skb) - inner_skb->head) -
net/core/gso.c +1 -0
@@ -117,6 +117,7 @@ struct sk_buff *__skb_gso_segment(struct sk_buff *skb,SKB_GSO_CB (skb)->mac_offset =skb_headroom (skb);SKB_GSO_CB (skb)->encap_level =0 ; +SKB_GSO_CB (skb)->recursion_counter =0 ;skb_reset_mac_header (skb);skb_reset_mac_len (skb);
net/ipv4/af_inet.c +3 -0
@@ -1380,6 +1380,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,int id;skb_reset_network_header (skb); +if (unlikely (gso_recursion_inc_test (skb, +IP_TUNNEL_RECURSION_LIMIT ))) + goto out; nhoff =skb_network_header (skb) -skb_mac_header (skb);if (unlikely (!pskb_may_pull (skb,sizeof (*iph)))) goto out;
net/ipv6/ip6_offload.c +4 -0
@@ -17,6 +17,7 @@#include <net/udp.h> #include <net/gro.h> #include <net/gso.h> +#include <net/ip_tunnels.h> #include "ip6_offload.h"
@@ -120,6 +121,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,bool gso_partial;skb_reset_network_header (skb); +if (unlikely (gso_recursion_inc_test (skb, +IP_TUNNEL_RECURSION_LIMIT ))) + goto out; err =ipv6_hopopt_jumbo_remove (skb);if (err)return ERR_PTR (err);
base-commit: 85d82e0d7c2dfd3e75d34f35b7ebbf41f45c9c4e
Signature · 1 line
2.53.0