Thread (2 messages) flat view 2 messages, 1 author, 1d ago
DORMANTno replies

Revision v4 of 4 in this series.

Revisions (4)
  1. v1 [diff vs current]
  2. v2 [diff vs current]
  3. v3 [diff vs current]
  4. v4 current

[PATCH net v4 1/1] net: gso: limit recursive IP-in-IP segmentation

From: Zihan Xi <hidden>
Date: 2026-09-22 07:12:04
Also in: lkml
Subsystem: networking [general], networking [ipv4/ipv6], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds

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.

The corresponding IPv6 stackable path was introduced separately by
"ipv6: gso: make ipv6_gso_segment() stackable". This patch uses the same
bound for IPv6, but the Fixes tag covers the IPv4 root cause only.

Limit the number of header bytes stripped from the original MAC header
before entering an IPv4 or IPv6 GSO handler to GSO_MAX_HEADER (256 bytes).
The existing skb_gso_cb->mac_offset records the original MAC header
offset; comparing it with current skb headroom gives the consumed header
offset without adding per-packet recursion state. Both IP GSO handlers
perform the check at entry, so direct IP re-entry and tunnel dispatch
that leads to another IP header share the same monotonic budget. Other
GSO callback entry points are unchanged.

GSO_MAX_HEADER is a practical header-offset budget, not an
architecture-independent stack-safety proof. The budget includes
link-layer/VLAN bytes already pulled from the original MAC header, while
IPv6 extension and tunnel headers consume it faster. Once pulled headers
reach 256 bytes, the next IPv4 or IPv6 GSO handler entry is rejected.

Fixes: 3347c9602955 ("ipv4: gso: make inet_gso_segment() stackable")
Cc: stable@vger.kernel.org
Reported-by: Vega <redacted>
Assisted-by: LLM
Co-developed-by: Luxing Yin <redacted>
Signed-off-by: Luxing Yin <redacted>
Signed-off-by: Zihan Xi <redacted>
---
changes in v4:
  - Keep the budget check only at the two IP GSO handler entries; remove
    the common callback wrapper and other GSO call-site checks.
  - Reuse skb_gso_cb->mac_offset and current skb headroom as the
    cumulative header-offset budget.
  - v3 Link: https://lore.kernel.org/all/cover.1789802623.git.zihanx@nebusec.ai/ (local)
 include/net/gso.h      | 8 ++++++++
 net/ipv4/af_inet.c     | 2 ++
 net/ipv6/ip6_offload.c | 2 ++
 3 files changed, 12 insertions(+)
diff --git a/include/net/gso.h b/include/net/gso.h
index 29975440c..86ff7e84c 100644
--- a/include/net/gso.h
+++ b/include/net/gso.h
@@ -23,6 +23,14 @@ struct skb_gso_cb {
 #define SKB_GSO_CB_OFFSET	32
 #define SKB_GSO_CB(skb) ((struct skb_gso_cb *)((skb)->cb + SKB_GSO_CB_OFFSET))
 
+#define GSO_MAX_HEADER	256
+
+static inline bool gso_header_len_exceeded(const struct sk_buff *skb)
+{
+	return skb_headroom(skb) - SKB_GSO_CB(skb)->mac_offset >=
+	       GSO_MAX_HEADER;
+}
+
 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/ipv4/af_inet.c b/net/ipv4/af_inet.c
index 32d006c1a..fa359b902 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -1375,6 +1375,8 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb,
 	int id;
 
 	skb_reset_network_header(skb);
+	if (unlikely(gso_header_len_exceeded(skb)))
+		goto out;
 	nhoff = skb_network_header(skb) - skb_mac_header(skb);
 	if (unlikely(!pskb_may_pull(skb, sizeof(*iph))))
 		goto out;
diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c
index 78f50c93c..884a7f827 100644
--- a/net/ipv6/ip6_offload.c
+++ b/net/ipv6/ip6_offload.c
@@ -104,6 +104,8 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
 	bool gso_partial;
 
 	skb_reset_network_header(skb);
+	if (unlikely(gso_header_len_exceeded(skb)))
+		goto out;
 	nhoff = skb_network_header(skb) - skb_mac_header(skb);
 	if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
 		goto out;
-- 
2.43.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help