* [PATCH net v3] net: preserve skb header offsets during headroom changes
@ 2026-11-13 23:50 Kyle Zeng
0 siblings, 0 replies; only message in thread
From: Kyle Zeng @ 2026-11-13 23:50 UTC (permalink / raw)
To: netdev; +Cc: davem, kuba, edumazet, pabeni, linux-kernel, Kyle Zeng, stable
An MPLS LWT route over a netkit device configured with large headroom
can make aligned skb expansion wrap the 16-bit network_header.
Neighbour output interprets the resulting negative skb_network_offset()
as a large unsigned pull length, causing eth_header() to write outside
the skb allocation.
Reject growth that cannot preserve the network, set MAC and transport,
recorded inner-network, or partial-checksum offsets. Do not relocate a
set MAC or transport header onto its unset value. Check the actual
post-pop positions in skb_mpls_pop(), bound the native MPLS label pop,
and reject excessive LWT headroom before reading the IP header.
The issue was found by an LLM.
Fixes: 1a37e412a022 ("net: Use 16bits for *_headers fields of struct skbuff")
Cc: stable@vger.kernel.org
Signed-off-by: Kyle Zeng <kylebot@openai.com>
---
Changes in v3:
- Check the actual post-pop MAC/network positions and reject the MAC
unset value in skb_mpls_pop().
- Check a set outer MAC header and a recorded inner network header
during positive relocation.
- Move the MPLS LWT entry check before reading the IP header.
v2: https://lore.kernel.org/netdev/20261110051258.64350-1-kylebot@openai.com/
v1: https://lore.kernel.org/netdev/20261030042246.74144-1-kylebot@openai.com/
I have a reproducer for this issue and can share via DM.
net/core/skbuff.c | 35 +++++++++++++++++++++++++++++++++++
net/mpls/af_mpls.c | 3 +++
net/mpls/mpls_iptunnel.c | 3 +++
3 files changed, 41 insertions(+)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 1ac1e327ac4c..bc836adafecd 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -2130,6 +2130,28 @@ struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask)
}
EXPORT_SYMBOL(skb_clone);
+static bool skb_header_offsets_fit(const struct sk_buff *skb,
+ unsigned int nhead)
+{
+ if (!nhead)
+ return true;
+ if (nhead > U16_MAX - skb->network_header)
+ return false;
+ if (skb_transport_header_was_set(skb) &&
+ nhead >= U16_MAX - skb->transport_header)
+ return false;
+ if (skb_mac_header_was_set(skb) &&
+ nhead >= U16_MAX - skb->mac_header)
+ return false;
+ if (skb->ip_summed == CHECKSUM_PARTIAL &&
+ nhead > U16_MAX - skb->csum_start)
+ return false;
+ if (skb_inner_network_header_was_set(skb) &&
+ nhead > U16_MAX - skb->inner_network_header)
+ return false;
+ return true;
+}
+
void skb_headers_offset_update(struct sk_buff *skb, int off)
{
/* Only adjust this if it actually is csum_start rather than csum */
@@ -2317,6 +2339,9 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail,
return -EINVAL;
}
+ if (unlikely(!skb_header_offsets_fit(skb, nhead)))
+ return -EINVAL;
+
if (unlikely(skb_shared(skb))) {
DEBUG_NET_WARN_ON_ONCE(1);
return -EINVAL;
@@ -2535,6 +2560,11 @@ struct sk_buff *skb_copy_expand(const struct sk_buff *skb,
return NULL;
oldheadroom = skb_headroom(skb);
+ if (unlikely(newheadroom > oldheadroom &&
+ !skb_header_offsets_fit(skb, (unsigned int)newheadroom -
+ oldheadroom)))
+ return NULL;
+
n = __alloc_skb(newheadroom + skb->len + newtailroom,
gfp_mask, skb_alloc_rx_flag(skb),
NUMA_NO_NODE);
@@ -6804,11 +6834,16 @@ EXPORT_SYMBOL_GPL(skb_mpls_push);
int skb_mpls_pop(struct sk_buff *skb, __be16 next_proto, int mac_len,
bool ethernet)
{
+ unsigned int headroom = skb_headroom(skb);
int err;
if (unlikely(!eth_p_mpls(skb->protocol)))
return 0;
+ if (unlikely(headroom >= U16_MAX - MPLS_HLEN ||
+ (unsigned int)mac_len > U16_MAX - MPLS_HLEN - headroom))
+ return -EINVAL;
+
err = skb_ensure_writable(skb, mac_len + MPLS_HLEN);
if (unlikely(err))
return err;
diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c
index ad4ad6f2001a..18c9654a6b36 100644
--- a/net/mpls/af_mpls.c
+++ b/net/mpls/af_mpls.c
@@ -419,6 +419,9 @@ static int mpls_forward(struct sk_buff *skb, struct net_device *dev,
if (!nh)
goto err;
+ if (unlikely(skb_headroom(skb) > U16_MAX - sizeof(*hdr)))
+ goto err;
+
/* Pop the label */
skb_pull(skb, sizeof(*hdr));
skb_reset_network_header(skb);
diff --git a/net/mpls/mpls_iptunnel.c b/net/mpls/mpls_iptunnel.c
index 1a1a0eb5b787..5123394234cc 100644
--- a/net/mpls/mpls_iptunnel.c
+++ b/net/mpls/mpls_iptunnel.c
@@ -59,6 +59,9 @@ static int mpls_xmit(struct sk_buff *skb)
!dst->lwtstate || skb_warn_if_lro(skb))
goto drop;
+ if (unlikely(skb_headroom(skb) > U16_MAX))
+ goto drop;
+
skb_forward_csum(skb);
tun_encap_info = mpls_lwtunnel_encap(dst->lwtstate);
base-commit: 2800868f63227c8237d70c41e70d441c919620d6
--
2.55.0.openai.911.g06873a30d2f3
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-13 23:50 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 23:50 [PATCH net v3] net: preserve skb header offsets during headroom changes Kyle Zeng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox