* [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation [not found] <cover.1789302084.git.zihanx@nebusec.ai> @ 2026-09-13 14:12 ` Zihan Xi 2026-09-13 22:46 ` Willem de Bruijn 0 siblings, 1 reply; 5+ messages in thread From: Zihan Xi @ 2026-09-13 14:12 UTC (permalink / raw) To: netdev Cc: linux-kernel, zihanx, davem, edumazet, kuba, pabeni, horms, dsahern, idosch, willemb, kuniyu, kees, richardbgobert, jiayuan.chen, stable, Vega, Luxing Yin IPIP GSO/TSO support makes IP-in-IP GSO dispatch re-enter inet_gso_segment() or ipv6_gso_segment() for every nested IP header. The only state that tracks this nesting is encap_level, which records header bytes and has no recursion bound. A sufficiently deep chain can consume the kernel stack before a transport GSO callback is reached. The unbounded callback nesting was introduced when inet_gso_segment() was made stackable by "ipv4: gso: make inet_gso_segment() stackable". GRE GSO support predated that change, and IP-in-IP GSO/TSO support later made the affected path reachable. Track the number of IP GSO callbacks in skb_gso_cb and reject the 15th callback entry. Thus 14 callback entries are allowed to complete; GSO_RECURSION_LIMIT is the rejection threshold, not the number of successful callbacks. Initialize the counter for each top-level GSO operation and check it in both IPv4 and IPv6 handlers so mixed IP-in-IP nesting is bounded. Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable") Cc: stable@vger.kernel.org Reported-by: Vega <vega@nebusec.ai> Assisted-by: LLM Co-developed-by: Luxing Yin <root@tr0jan.top> Signed-off-by: Luxing Yin <root@tr0jan.top> Signed-off-by: Zihan Xi <zihanx@nebusec.ai> --- include/net/gso.h | 9 +++++++++ net/core/gso.c | 1 + net/ipv4/af_inet.c | 3 +++ net/ipv6/ip6_offload.c | 3 +++ 4 files changed, 16 insertions(+) diff --git a/include/net/gso.h b/include/net/gso.h index 29975440cad5..2665acbb9205 100644 --- a/include/net/gso.h +++ b/include/net/gso.h @@ -19,10 +19,19 @@ struct skb_gso_cb { int encap_level; __wsum csum; __u16 csum_start; + /* Number of GSO callbacks this packet already went through. */ + u8 recursion_counter; }; #define SKB_GSO_CB_OFFSET 32 #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) +#define GSO_RECURSION_LIMIT 15 /* First callback depth to reject. */ +static inline int gso_recursion_inc_test(struct sk_buff *skb) +{ + return ++SKB_GSO_CB(skb)->recursion_counter == + GSO_RECURSION_LIMIT; +} + static inline int skb_tnl_header_len(const struct sk_buff *inner_skb) { return (skb_mac_header(inner_skb) - inner_skb->head) - diff --git a/net/core/gso.c b/net/core/gso.c index bcd156372f4d..e96ef6350064 100644 --- a/net/core/gso.c +++ b/net/core/gso.c @@ -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); diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 32d006c1a8ee..2bd88ba05eb9 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -1374,6 +1374,9 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb, int ihl; int id; + if (unlikely(gso_recursion_inc_test(skb))) + goto out; + skb_reset_network_header(skb); nhoff = skb_network_header(skb) - skb_mac_header(skb); if (unlikely(!pskb_may_pull(skb, sizeof(*iph)))) diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c index 78f50c93c536..391527a1a47b 100644 --- a/net/ipv6/ip6_offload.c +++ b/net/ipv6/ip6_offload.c @@ -103,6 +103,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int nhoff; bool gso_partial; + if (unlikely(gso_recursion_inc_test(skb))) + goto out; + skb_reset_network_header(skb); nhoff = skb_network_header(skb) - skb_mac_header(skb); if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h)))) -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation 2026-09-13 14:12 ` [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation Zihan Xi @ 2026-09-13 22:46 ` Willem de Bruijn 2026-09-13 23:08 ` Eric Dumazet 2026-09-14 8:03 ` David Laight 0 siblings, 2 replies; 5+ messages in thread From: Willem de Bruijn @ 2026-09-13 22:46 UTC (permalink / raw) To: Zihan Xi, netdev Cc: linux-kernel, zihanx, davem, edumazet, kuba, pabeni, horms, dsahern, idosch, willemb, kuniyu, kees, richardbgobert, jiayuan.chen, stable, Vega, Luxing Yin Zihan Xi wrote: > IPIP GSO/TSO support makes IP-in-IP GSO dispatch re-enter > inet_gso_segment() or ipv6_gso_segment() for every nested IP header. The > only state that tracks this nesting is encap_level, which records header > bytes and has no recursion bound. A sufficiently deep chain can consume the > kernel stack before a transport GSO callback is reached. > > The unbounded callback nesting was introduced when inet_gso_segment() was > made stackable by "ipv4: gso: make inet_gso_segment() stackable". GRE GSO > support predated that change, and IP-in-IP GSO/TSO support later made the > affected path reachable. > > Track the number of IP GSO callbacks in skb_gso_cb and reject the 15th > callback entry. Thus 14 callback entries are allowed to complete; > GSO_RECURSION_LIMIT is the rejection threshold, not the number of > successful callbacks. Initialize the counter for each top-level GSO > operation and check it in both IPv4 and IPv6 handlers so mixed IP-in-IP > nesting is bounded. > > Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable") > Cc: stable@vger.kernel.org > Reported-by: Vega <vega@nebusec.ai> > Assisted-by: LLM > Co-developed-by: Luxing Yin <root@tr0jan.top> > Signed-off-by: Luxing Yin <root@tr0jan.top> > Signed-off-by: Zihan Xi <zihanx@nebusec.ai> > --- > include/net/gso.h | 9 +++++++++ > net/core/gso.c | 1 + > net/ipv4/af_inet.c | 3 +++ > net/ipv6/ip6_offload.c | 3 +++ > 4 files changed, 16 insertions(+) > > diff --git a/include/net/gso.h b/include/net/gso.h > index 29975440cad5..2665acbb9205 100644 > --- a/include/net/gso.h > +++ b/include/net/gso.h > @@ -19,10 +19,19 @@ struct skb_gso_cb { > int encap_level; > __wsum csum; > __u16 csum_start; > + /* Number of GSO callbacks this packet already went through. */ > + u8 recursion_counter; > }; > #define SKB_GSO_CB_OFFSET 32 > #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) > > +#define GSO_RECURSION_LIMIT 15 /* First callback depth to reject. */ > +static inline int gso_recursion_inc_test(struct sk_buff *skb) What is 15 based on? Is that where in your test stack overflow occurs? A realistic practical limit would likely already be smaller. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation 2026-09-13 22:46 ` Willem de Bruijn @ 2026-09-13 23:08 ` Eric Dumazet 2026-09-14 3:43 ` zihan xi 2026-09-14 8:03 ` David Laight 1 sibling, 1 reply; 5+ messages in thread From: Eric Dumazet @ 2026-09-13 23:08 UTC (permalink / raw) To: Willem de Bruijn Cc: Zihan Xi, netdev, linux-kernel, davem, kuba, pabeni, horms, dsahern, idosch, willemb, kuniyu, kees, richardbgobert, jiayuan.chen, stable, Vega, Luxing Yin On Sun, Sep 13, 2026 at 3:46 PM Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote: > > Zihan Xi wrote: > > IPIP GSO/TSO support makes IP-in-IP GSO dispatch re-enter > > inet_gso_segment() or ipv6_gso_segment() for every nested IP header. The > > only state that tracks this nesting is encap_level, which records header > > bytes and has no recursion bound. A sufficiently deep chain can consume the > > kernel stack before a transport GSO callback is reached. > > > > The unbounded callback nesting was introduced when inet_gso_segment() was > > made stackable by "ipv4: gso: make inet_gso_segment() stackable". GRE GSO > > support predated that change, and IP-in-IP GSO/TSO support later made the > > affected path reachable. > > > > Track the number of IP GSO callbacks in skb_gso_cb and reject the 15th > > callback entry. Thus 14 callback entries are allowed to complete; > > GSO_RECURSION_LIMIT is the rejection threshold, not the number of > > successful callbacks. Initialize the counter for each top-level GSO > > operation and check it in both IPv4 and IPv6 handlers so mixed IP-in-IP > > nesting is bounded. > > > > Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable") > > Cc: stable@vger.kernel.org > > Reported-by: Vega <vega@nebusec.ai> > > Assisted-by: LLM > > Co-developed-by: Luxing Yin <root@tr0jan.top> > > Signed-off-by: Luxing Yin <root@tr0jan.top> > > Signed-off-by: Zihan Xi <zihanx@nebusec.ai> > > --- > > include/net/gso.h | 9 +++++++++ > > net/core/gso.c | 1 + > > net/ipv4/af_inet.c | 3 +++ > > net/ipv6/ip6_offload.c | 3 +++ > > 4 files changed, 16 insertions(+) > > > > diff --git a/include/net/gso.h b/include/net/gso.h > > index 29975440cad5..2665acbb9205 100644 > > --- a/include/net/gso.h > > +++ b/include/net/gso.h > > @@ -19,10 +19,19 @@ struct skb_gso_cb { > > int encap_level; > > __wsum csum; > > __u16 csum_start; > > + /* Number of GSO callbacks this packet already went through. */ > > + u8 recursion_counter; > > }; > > #define SKB_GSO_CB_OFFSET 32 > > #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) > > > > +#define GSO_RECURSION_LIMIT 15 /* First callback depth to reject. */ > > +static inline int gso_recursion_inc_test(struct sk_buff *skb) > > What is 15 based on? Is that where in your test stack overflow occurs? > > A realistic practical limit would likely already be smaller. An alternative would be to limit total header sizes (nhoff) to 256 bytes or so. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation 2026-09-13 23:08 ` Eric Dumazet @ 2026-09-14 3:43 ` zihan xi 0 siblings, 0 replies; 5+ messages in thread From: zihan xi @ 2026-09-14 3:43 UTC (permalink / raw) To: Eric Dumazet Cc: Willem de Bruijn, netdev, linux-kernel, davem, kuba, pabeni, horms, dsahern, idosch, willemb, kuniyu, kees, richardbgobert, jiayuan.chen, stable, Vega, Luxing Yin On Mon, Sep 14, 2026 at 7:08 AM Eric Dumazet <edumazet@google.com> wrote: > > On Sun, Sep 13, 2026 at 3:46 PM Willem de Bruijn > <willemdebruijn.kernel@gmail.com> wrote: > > > > Zihan Xi wrote: > > > IPIP GSO/TSO support makes IP-in-IP GSO dispatch re-enter > > > inet_gso_segment() or ipv6_gso_segment() for every nested IP header. The > > > only state that tracks this nesting is encap_level, which records header > > > bytes and has no recursion bound. A sufficiently deep chain can consume the > > > kernel stack before a transport GSO callback is reached. > > > > > > The unbounded callback nesting was introduced when inet_gso_segment() was > > > made stackable by "ipv4: gso: make inet_gso_segment() stackable". GRE GSO > > > support predated that change, and IP-in-IP GSO/TSO support later made the > > > affected path reachable. > > > > > > Track the number of IP GSO callbacks in skb_gso_cb and reject the 15th > > > callback entry. Thus 14 callback entries are allowed to complete; > > > GSO_RECURSION_LIMIT is the rejection threshold, not the number of > > > successful callbacks. Initialize the counter for each top-level GSO > > > operation and check it in both IPv4 and IPv6 handlers so mixed IP-in-IP > > > nesting is bounded. > > > > > > Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable") > > > Cc: stable@vger.kernel.org > > > Reported-by: Vega <vega@nebusec.ai> > > > Assisted-by: LLM > > > Co-developed-by: Luxing Yin <root@tr0jan.top> > > > Signed-off-by: Luxing Yin <root@tr0jan.top> > > > Signed-off-by: Zihan Xi <zihanx@nebusec.ai> > > > --- > > > include/net/gso.h | 9 +++++++++ > > > net/core/gso.c | 1 + > > > net/ipv4/af_inet.c | 3 +++ > > > net/ipv6/ip6_offload.c | 3 +++ > > > 4 files changed, 16 insertions(+) > > > > > > diff --git a/include/net/gso.h b/include/net/gso.h > > > index 29975440cad5..2665acbb9205 100644 > > > --- a/include/net/gso.h > > > +++ b/include/net/gso.h > > > @@ -19,10 +19,19 @@ struct skb_gso_cb { > > > int encap_level; > > > __wsum csum; > > > __u16 csum_start; > > > + /* Number of GSO callbacks this packet already went through. */ > > > + u8 recursion_counter; > > > }; > > > #define SKB_GSO_CB_OFFSET 32 > > > #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) > > > > > > +#define GSO_RECURSION_LIMIT 15 /* First callback depth to reject. */ > > > +static inline int gso_recursion_inc_test(struct sk_buff *skb) > > > > What is 15 based on? Is that where in your test stack overflow occurs? > > > > A realistic practical limit would likely already be smaller. > > An alternative would be to limit total header sizes (nhoff) to 256 bytes or so. Thanks for the feedback. > What is 15 based on? Is that where in your test stack overflow occurs? No. The reproducer uses a much deeper header chain, and the test did not establish 15 as a meaningful boundary. It was an arbitrary callback-depth limit in the initial version, so I have removed that approach. > An alternative would be to limit total header sizes (nhoff) to 256 bytes or so. Following this suggestion, I changed the fix to bound the cumulative nhoff in both inet_gso_segment() and ipv6_gso_segment(). If nhoff exceeds 256 bytes, the existing -EINVAL path is used. This bounds recursive IP GSO by cumulative header size rather than by an assumed callback count, without adding state to skb_gso_cb. The 256-byte value is a practical upper bound on the cumulative header offset, not a measured stack-budget threshold. I am rebuilding this revision and will rerun the reproducer against both the fixed and unpatched kernels before sending the next version. Thanks, Zihan ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation 2026-09-13 22:46 ` Willem de Bruijn 2026-09-13 23:08 ` Eric Dumazet @ 2026-09-14 8:03 ` David Laight 1 sibling, 0 replies; 5+ messages in thread From: David Laight @ 2026-09-14 8:03 UTC (permalink / raw) To: Willem de Bruijn Cc: Zihan Xi, netdev, linux-kernel, davem, edumazet, kuba, pabeni, horms, dsahern, idosch, willemb, kuniyu, kees, richardbgobert, jiayuan.chen, stable, Vega, Luxing Yin On Sun, 13 Sep 2026 18:46:53 -0400 Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote: > Zihan Xi wrote: > > IPIP GSO/TSO support makes IP-in-IP GSO dispatch re-enter > > inet_gso_segment() or ipv6_gso_segment() for every nested IP header. The > > only state that tracks this nesting is encap_level, which records header > > bytes and has no recursion bound. A sufficiently deep chain can consume the > > kernel stack before a transport GSO callback is reached. > > > > The unbounded callback nesting was introduced when inet_gso_segment() was > > made stackable by "ipv4: gso: make inet_gso_segment() stackable". GRE GSO > > support predated that change, and IP-in-IP GSO/TSO support later made the > > affected path reachable. > > > > Track the number of IP GSO callbacks in skb_gso_cb and reject the 15th > > callback entry. Thus 14 callback entries are allowed to complete; > > GSO_RECURSION_LIMIT is the rejection threshold, not the number of > > successful callbacks. Initialize the counter for each top-level GSO > > operation and check it in both IPv4 and IPv6 handlers so mixed IP-in-IP > > nesting is bounded. > > > > Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable") > > Cc: stable@vger.kernel.org > > Reported-by: Vega <vega@nebusec.ai> > > Assisted-by: LLM > > Co-developed-by: Luxing Yin <root@tr0jan.top> > > Signed-off-by: Luxing Yin <root@tr0jan.top> > > Signed-off-by: Zihan Xi <zihanx@nebusec.ai> > > --- > > include/net/gso.h | 9 +++++++++ > > net/core/gso.c | 1 + > > net/ipv4/af_inet.c | 3 +++ > > net/ipv6/ip6_offload.c | 3 +++ > > 4 files changed, 16 insertions(+) > > > > diff --git a/include/net/gso.h b/include/net/gso.h > > index 29975440cad5..2665acbb9205 100644 > > --- a/include/net/gso.h > > +++ b/include/net/gso.h > > @@ -19,10 +19,19 @@ struct skb_gso_cb { > > int encap_level; > > __wsum csum; > > __u16 csum_start; > > + /* Number of GSO callbacks this packet already went through. */ > > + u8 recursion_counter; > > }; > > #define SKB_GSO_CB_OFFSET 32 > > #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET)) > > > > +#define GSO_RECURSION_LIMIT 15 /* First callback depth to reject. */ > > +static inline int gso_recursion_inc_test(struct sk_buff *skb) > > What is 15 based on? Is that where in your test stack overflow occurs? > > A realistic practical limit would likely already be smaller. > What is the recursion limit on sparc64? (The minimum stack frame is 176 bytes.) It would be more sensible to check the 'amount of stack remaining' than the number of levels of recursion. Even though that is still asking 'how long is a piece of string'. David ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-14 8:03 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <cover.1789302084.git.zihanx@nebusec.ai>
2026-09-13 14:12 ` [PATCH net 1/1] net: gso: limit recursive IP-in-IP segmentation Zihan Xi
2026-09-13 22:46 ` Willem de Bruijn
2026-09-13 23:08 ` Eric Dumazet
2026-09-14 3:43 ` zihan xi
2026-09-14 8:03 ` David Laight
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox