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.

Fixes: 3347c96029 ("ipv4: gso: make inet_gso_segment() stackable")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Closes: https://lore.kernel.org/all/cover.1790157745.git.zihanx@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>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20260924051521.32568-2-zihanx@nebusec.ai
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Zihan Xi 2026-09-24 05:15:21 +00:00 committed by Paolo Abeni
parent 0f17d2e3dd
commit 006c8b834a
4 changed files with 16 additions and 0 deletions

View File

@ -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) -

View File

@ -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);

View File

@ -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;

View File

@ -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;