Linux kernel -stable discussions
 help / color / mirror / Atom feed
* [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