* [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation
@ 2026-08-13 3:38 Dong Chenchen
2026-08-13 4:39 ` Eric Dumazet
0 siblings, 1 reply; 6+ messages in thread
From: Dong Chenchen @ 2026-08-13 3:38 UTC (permalink / raw)
To: davem, edumazet, pabeni, kuba, horms, herbert, kuniyu
Cc: idosch, andrew+netdev, ap420073, steffen.klassert, laforge,
jiayuan.chen, jhs, zhangchangzhong, netdev, Dong Chenchen,
syzbot+83181a31faf9455499c5
Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale
transport_header offset after tunnel decapsulation.
BUG: unable to handle page fault for address: ffffed102091a42e
Oops: Oops: 0000 [#1] SMP KASAN NOPTI
CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:__asan_load2
<IRQ>
qdisc_pkt_len_segs_init (net/core/dev.c:4145)
__dev_queue_xmit (net/core/dev.c:4787)
br_dev_queue_push_xmit (net/bridge/br_forward.c:53)
br_handle_frame_finish (net/bridge/br_input.c:229)
br_handle_frame (net/bridge/br_input.c:315)
__netif_receive_skb_core.constprop.0 (net/core/dev.c:6099)
__netif_receive_skb_list_core (net/core/dev.c:6287)
netif_receive_skb_list_internal (net/core/dev.c:6445)
napi_complete_done (net/core/dev.c:6813)
gro_cell_poll (net/core/gro_cells.c:74)
__napi_poll (net/core/dev.c:7735)
net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955)
handle_softirqs (kernel/softirq.c:622)
do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 )
__local_bh_enable_ip (kernel/softirq.c:450)
tun_get_user (drivers/net/tun.c:1986 (discriminator 1))
tun_chr_write_iter (drivers/net/tun.c:2032)
The crash requires four conditions to line up:
1. The incoming packet is encapsulated and carries GSO metadata. The outer
transport header offset is stored in skb->transport_header while the
packet is still in the outer tunnel context.
2. The tunnel receiver strips the outer headers. skb->data is advanced to
the inner frame, but skb->transport_header is left pointing to the
now-removed outer L4 header, so it becomes a negative offset relative to
the new data.
3. The inner frame is not delivered to the local IP stack. Instead, it
is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and
the transport header is not reset to the inner L4 offset.
4. The forwarding path calls __dev_queue_xmit(), which enters
qdisc_pkt_len_segs_init(). That function computes the GSO header length
from skb_transport_offset(skb). Because the offset is negative, the
unsigned cast overflows and pskb_may_pull(skb, hdr_len +
sizeof(struct tcphdr)) reads past the end of the skb, triggering a
KASAN fault or page fault.
Fix this by clearing skb->transport_header to the ~0U sentinel at the
tunnel decapsulation boundary, after each tunnel receive function has
finished all processing that needs the outer L4 header and before the skb
is handed to GRO or the stack. The IP/GRO receive paths then set the
transport header correctly when they parse the inner packet.
Fixes: 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()")
Reported-by: syzbot+83181a31faf9455499c5@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/69de2bee.a00a0220.475f0.0041.GAE@google.com/T/
Signed-off-by: Dong Chenchen <dongchenchen2@huawei.com>
---
drivers/net/amt.c | 1 +
drivers/net/bareudp.c | 2 ++
drivers/net/geneve.c | 6 ++++--
drivers/net/gtp.c | 1 +
drivers/net/pfcp.c | 1 +
drivers/net/vxlan/vxlan_core.c | 1 +
include/linux/skbuff.h | 5 +++++
net/ipv4/ip_tunnel.c | 2 ++
net/ipv6/ip6_tunnel.c | 2 ++
net/ipv6/sit.c | 1 +
net/xfrm/xfrm_input.c | 1 +
11 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 182a41d59a75..5968a08fcd5c 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -2355,6 +2355,7 @@ static bool amt_multicast_data_handler(struct amt_dev *amt, struct sk_buff *skb)
skb->pkt_type = PACKET_MULTICAST;
skb->ip_summed = CHECKSUM_NONE;
+ skb_unset_transport_header(skb);
len = skb->len;
err = gro_cells_receive(&amt->gro_cells, skb);
if (likely(err == NET_RX_SUCCESS))
diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c
index 5ef841c85526..b92652ca91ec 100644
--- a/drivers/net/bareudp.c
+++ b/drivers/net/bareudp.c
@@ -191,6 +191,8 @@ static int bareudp_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
}
}
+ skb_unset_transport_header(skb);
+
len = skb->len;
err = gro_cells_receive(&bareudp->gro_cells, skb);
if (likely(err == NET_RX_SUCCESS))
diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c
index 72023ebd0e1b..b632239c5e43 100644
--- a/drivers/net/geneve.c
+++ b/drivers/net/geneve.c
@@ -372,10 +372,12 @@ static void geneve_rx(struct geneve_dev *geneve, struct geneve_sock *gs,
/* Skip the additional GRO stage when hints are in use. */
len = skb->len;
- if (skb->encapsulation)
+ if (skb->encapsulation) {
err = netif_rx(skb);
- else
+ } else {
+ skb_unset_transport_header(skb);
err = gro_cells_receive(&geneve->gro_cells, skb);
+ }
if (likely(err == NET_RX_SUCCESS))
dev_dstats_rx_add(geneve->dev, len);
diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index 9a12cc53da00..1e3013d49713 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -337,6 +337,7 @@ static int gtp_rx(struct pdp_ctx *pctx, struct sk_buff *skb,
dev_sw_netstats_rx_add(pctx->dev, skb->len);
+ skb_unset_transport_header(skb);
__netif_rx(skb);
return 0;
diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c
index d8e4d60f5834..4b67906646ff 100644
--- a/drivers/net/pfcp.c
+++ b/drivers/net/pfcp.c
@@ -94,6 +94,7 @@ static int pfcp_encap_recv(struct sock *sk, struct sk_buff *skb)
skb_reset_mac_header(skb);
skb->dev = pfcp->dev;
+ skb_unset_transport_header(skb);
gro_cells_receive(&pfcp->gro_cells, skb);
return 0;
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 1ded27768a97..9366895856f6 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -1799,6 +1799,7 @@ static int vxlan_rcv(struct sock *sk, struct sk_buff *skb)
dev_dstats_rx_add(vxlan->dev, skb->len);
vxlan_vnifilter_count(vxlan, vni, vninode, VXLAN_VNI_STATS_RX, skb->len);
+ skb_unset_transport_header(skb);
gro_cells_receive(&vxlan->gro_cells, skb);
rcu_read_unlock();
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 22eda1d54a0e..626bbb9bae1a 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -3082,6 +3082,11 @@ static inline bool skb_transport_header_was_set(const struct sk_buff *skb)
return skb->transport_header != (typeof(skb->transport_header))~0U;
}
+static inline void skb_unset_transport_header(struct sk_buff *skb)
+{
+ skb->transport_header = (typeof(skb->transport_header))~0U;
+}
+
static inline unsigned char *skb_transport_header(const struct sk_buff *skb)
{
DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb));
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index 9d114bd575f9..5e677c86f0e3 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -445,6 +445,8 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
if (tun_dst)
skb_dst_set(skb, (struct dst_entry *)tun_dst);
+ skb_unset_transport_header(skb);
+
gro_cells_receive(&tunnel->gro_cells, skb);
return 0;
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c
index ebf83f090376..e7c8283a0e39 100644
--- a/net/ipv6/ip6_tunnel.c
+++ b/net/ipv6/ip6_tunnel.c
@@ -892,6 +892,8 @@ static int __ip6_tnl_rcv(struct ip6_tnl *tunnel, struct sk_buff *skb,
if (tun_dst)
skb_dst_set(skb, (struct dst_entry *)tun_dst);
+ skb_unset_transport_header(skb);
+
gro_cells_receive(&tunnel->gro_cells, skb);
return 0;
diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c
index a38b24fb8384..ad5adc5abe6e 100644
--- a/net/ipv6/sit.c
+++ b/net/ipv6/sit.c
@@ -726,6 +726,7 @@ static int ipip6_rcv(struct sk_buff *skb)
dev_sw_netstats_rx_add(tunnel->dev, skb->len);
+ skb_unset_transport_header(skb);
netif_rx(skb);
return 0;
diff --git a/net/xfrm/xfrm_input.c b/net/xfrm/xfrm_input.c
index eecab337bd0a..2767021c138a 100644
--- a/net/xfrm/xfrm_input.c
+++ b/net/xfrm/xfrm_input.c
@@ -744,6 +744,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
skb_dst_drop(skb);
if (async)
dev_put(dev);
+ skb_unset_transport_header(skb);
gro_cells_receive(&gro_cells, skb);
rcu_read_unlock();
return 0;
--
2.25.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation
2026-08-13 3:38 [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation Dong Chenchen
@ 2026-08-13 4:39 ` Eric Dumazet
2026-08-19 13:11 ` Eric Dumazet
2026-08-20 8:58 ` dongchenchen (A)
0 siblings, 2 replies; 6+ messages in thread
From: Eric Dumazet @ 2026-08-13 4:39 UTC (permalink / raw)
To: Dong Chenchen
Cc: davem, pabeni, kuba, horms, herbert, kuniyu, idosch,
andrew+netdev, ap420073, steffen.klassert, laforge, jiayuan.chen,
jhs, zhangchangzhong, netdev, syzbot+83181a31faf9455499c5
On Thu, Aug 13, 2026 at 5:30 AM Dong Chenchen <dongchenchen2@huawei.com> wrote:
>
> Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale
> transport_header offset after tunnel decapsulation.
>
> BUG: unable to handle page fault for address: ffffed102091a42e
> Oops: Oops: 0000 [#1] SMP KASAN NOPTI
> CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full)
> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> RIP: 0010:__asan_load2
> <IRQ>
> qdisc_pkt_len_segs_init (net/core/dev.c:4145)
> __dev_queue_xmit (net/core/dev.c:4787)
> br_dev_queue_push_xmit (net/bridge/br_forward.c:53)
> br_handle_frame_finish (net/bridge/br_input.c:229)
> br_handle_frame (net/bridge/br_input.c:315)
> __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099)
> __netif_receive_skb_list_core (net/core/dev.c:6287)
> netif_receive_skb_list_internal (net/core/dev.c:6445)
> napi_complete_done (net/core/dev.c:6813)
> gro_cell_poll (net/core/gro_cells.c:74)
> __napi_poll (net/core/dev.c:7735)
> net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955)
> handle_softirqs (kernel/softirq.c:622)
> do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 )
> __local_bh_enable_ip (kernel/softirq.c:450)
> tun_get_user (drivers/net/tun.c:1986 (discriminator 1))
> tun_chr_write_iter (drivers/net/tun.c:2032)
>
> The crash requires four conditions to line up:
>
> 1. The incoming packet is encapsulated and carries GSO metadata. The outer
> transport header offset is stored in skb->transport_header while the
> packet is still in the outer tunnel context.
> 2. The tunnel receiver strips the outer headers. skb->data is advanced to
> the inner frame, but skb->transport_header is left pointing to the
> now-removed outer L4 header, so it becomes a negative offset relative to
> the new data.
> 3. The inner frame is not delivered to the local IP stack. Instead, it
> is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and
> the transport header is not reset to the inner L4 offset.
> 4. The forwarding path calls __dev_queue_xmit(), which enters
> qdisc_pkt_len_segs_init(). That function computes the GSO header length
> from skb_transport_offset(skb). Because the offset is negative, the
> unsigned cast overflows and pskb_may_pull(skb, hdr_len +
> sizeof(struct tcphdr)) reads past the end of the skb, triggering a
> KASAN fault or page fault.
>
> Fix this by clearing skb->transport_header to the ~0U sentinel at the
> tunnel decapsulation boundary, after each tunnel receive function has
> finished all processing that needs the outer L4 header and before the skb
> is handed to GRO or the stack. The IP/GRO receive paths then set the
> transport header correctly when they parse the inner packet.
>
> Fixes: 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()")
This Fixes: tag seems pretty random to me. Issue was present long
before that commit.
Next time, please be precise with the `Fixes:` tag; it's not decoration.
Also, calling skb_unset_transport_header(skb) directly from
gro_cells_receive() would make more sense and
your patch would be less intrusive.
pw-bot: cr
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation
2026-08-13 4:39 ` Eric Dumazet
@ 2026-08-19 13:11 ` Eric Dumazet
2026-08-20 8:58 ` dongchenchen (A)
1 sibling, 0 replies; 6+ messages in thread
From: Eric Dumazet @ 2026-08-19 13:11 UTC (permalink / raw)
To: Dong Chenchen
Cc: davem, pabeni, kuba, horms, herbert, kuniyu, idosch,
andrew+netdev, ap420073, steffen.klassert, laforge, jiayuan.chen,
jhs, zhangchangzhong, netdev, syzbot+83181a31faf9455499c5
On Thu, Aug 13, 2026 at 6:39 AM Eric Dumazet <edumazet@google.com> wrote:
>
> On Thu, Aug 13, 2026 at 5:30 AM Dong Chenchen <dongchenchen2@huawei.com> wrote:
> >
> > Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale
> > transport_header offset after tunnel decapsulation.
> >
> > BUG: unable to handle page fault for address: ffffed102091a42e
> > Oops: Oops: 0000 [#1] SMP KASAN NOPTI
> > CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full)
> > Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> > RIP: 0010:__asan_load2
> > <IRQ>
> > qdisc_pkt_len_segs_init (net/core/dev.c:4145)
> > __dev_queue_xmit (net/core/dev.c:4787)
> > br_dev_queue_push_xmit (net/bridge/br_forward.c:53)
> > br_handle_frame_finish (net/bridge/br_input.c:229)
> > br_handle_frame (net/bridge/br_input.c:315)
> > __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099)
> > __netif_receive_skb_list_core (net/core/dev.c:6287)
> > netif_receive_skb_list_internal (net/core/dev.c:6445)
> > napi_complete_done (net/core/dev.c:6813)
> > gro_cell_poll (net/core/gro_cells.c:74)
> > __napi_poll (net/core/dev.c:7735)
> > net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955)
> > handle_softirqs (kernel/softirq.c:622)
> > do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 )
> > __local_bh_enable_ip (kernel/softirq.c:450)
> > tun_get_user (drivers/net/tun.c:1986 (discriminator 1))
> > tun_chr_write_iter (drivers/net/tun.c:2032)
> >
> > The crash requires four conditions to line up:
> >
> > 1. The incoming packet is encapsulated and carries GSO metadata. The outer
> > transport header offset is stored in skb->transport_header while the
> > packet is still in the outer tunnel context.
> > 2. The tunnel receiver strips the outer headers. skb->data is advanced to
> > the inner frame, but skb->transport_header is left pointing to the
> > now-removed outer L4 header, so it becomes a negative offset relative to
> > the new data.
> > 3. The inner frame is not delivered to the local IP stack. Instead, it
> > is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and
> > the transport header is not reset to the inner L4 offset.
> > 4. The forwarding path calls __dev_queue_xmit(), which enters
> > qdisc_pkt_len_segs_init(). That function computes the GSO header length
> > from skb_transport_offset(skb). Because the offset is negative, the
> > unsigned cast overflows and pskb_may_pull(skb, hdr_len +
> > sizeof(struct tcphdr)) reads past the end of the skb, triggering a
> > KASAN fault or page fault.
> >
> > Fix this by clearing skb->transport_header to the ~0U sentinel at the
> > tunnel decapsulation boundary, after each tunnel receive function has
> > finished all processing that needs the outer L4 header and before the skb
> > is handed to GRO or the stack. The IP/GRO receive paths then set the
> > transport header correctly when they parse the inner packet.
> >
> > Fixes: 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()")
>
> This Fixes: tag seems pretty random to me. Issue was present long
> before that commit.
> Next time, please be precise with the `Fixes:` tag; it's not decoration.
>
> Also, calling skb_unset_transport_header(skb) directly from
> gro_cells_receive() would make more sense and
> your patch would be less intrusive.
Do you plan sending a V2 ?
This would merely be a one-liner.
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 22eda1d54a0e8490c3b64a733cc93a30aaa84a54..626bbb9bae1ad4ea6657f5e0f581cca0f49f16ed
100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -3082,6 +3082,11 @@ static inline bool
skb_transport_header_was_set(const struct sk_buff *skb)
return skb->transport_header != (typeof(skb->transport_header))~0U;
}
+static inline void skb_unset_transport_header(struct sk_buff *skb)
+{
+ skb->transport_header = (typeof(skb->transport_header))~0U;
+}
+
static inline unsigned char *skb_transport_header(const struct sk_buff *skb)
{
DEBUG_NET_WARN_ON_ONCE(!skb_transport_header_was_set(skb));
diff --git a/net/core/gro_cells.c b/net/core/gro_cells.c
index 1b84385c04bd9c4539cbcc6140867dd98e229f61..d8c0a2867120199cec1525034773650f1ff24542
100644
--- a/net/core/gro_cells.c
+++ b/net/core/gro_cells.c
@@ -22,6 +22,8 @@ int gro_cells_receive(struct gro_cells *gcells,
struct sk_buff *skb)
if (unlikely(!(dev->flags & IFF_UP)))
goto drop;
+ skb_unset_transport_header(skb);
+
if (!gcells->cells || skb_cloned(skb) || netif_elide_gro(dev)) {
res = netif_rx(skb);
goto unlock;
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation
2026-08-13 4:39 ` Eric Dumazet
2026-08-19 13:11 ` Eric Dumazet
@ 2026-08-20 8:58 ` dongchenchen (A)
2026-08-20 9:16 ` Eric Dumazet
1 sibling, 1 reply; 6+ messages in thread
From: dongchenchen (A) @ 2026-08-20 8:58 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, pabeni, kuba, horms, herbert, kuniyu, idosch,
andrew+netdev, ap420073, steffen.klassert, laforge, jiayuan.chen,
jhs, zhangchangzhong, netdev, syzbot+83181a31faf9455499c5
> On Thu, Aug 13, 2026 at 5:30 AM Dong Chenchen <dongchenchen2@huawei.com> wrote:
>>
>> Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale
>> transport_header offset after tunnel decapsulation.
>>
>> BUG: unable to handle page fault for address: ffffed102091a42e
>> Oops: Oops: 0000 [#1] SMP KASAN NOPTI
>> CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full)
>> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
>> RIP: 0010:__asan_load2
>> <IRQ>
>> qdisc_pkt_len_segs_init (net/core/dev.c:4145)
>> __dev_queue_xmit (net/core/dev.c:4787)
>> br_dev_queue_push_xmit (net/bridge/br_forward.c:53)
>> br_handle_frame_finish (net/bridge/br_input.c:229)
>> br_handle_frame (net/bridge/br_input.c:315)
>> __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099)
>> __netif_receive_skb_list_core (net/core/dev.c:6287)
>> netif_receive_skb_list_internal (net/core/dev.c:6445)
>> napi_complete_done (net/core/dev.c:6813)
>> gro_cell_poll (net/core/gro_cells.c:74)
>> __napi_poll (net/core/dev.c:7735)
>> net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955)
>> handle_softirqs (kernel/softirq.c:622)
>> do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 )
>> __local_bh_enable_ip (kernel/softirq.c:450)
>> tun_get_user (drivers/net/tun.c:1986 (discriminator 1))
>> tun_chr_write_iter (drivers/net/tun.c:2032)
>>
>> The crash requires four conditions to line up:
>>
>> 1. The incoming packet is encapsulated and carries GSO metadata. The outer
>> transport header offset is stored in skb->transport_header while the
>> packet is still in the outer tunnel context.
>> 2. The tunnel receiver strips the outer headers. skb->data is advanced to
>> the inner frame, but skb->transport_header is left pointing to the
>> now-removed outer L4 header, so it becomes a negative offset relative to
>> the new data.
>> 3. The inner frame is not delivered to the local IP stack. Instead, it
>> is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and
>> the transport header is not reset to the inner L4 offset.
>> 4. The forwarding path calls __dev_queue_xmit(), which enters
>> qdisc_pkt_len_segs_init(). That function computes the GSO header length
>> from skb_transport_offset(skb). Because the offset is negative, the
>> unsigned cast overflows and pskb_may_pull(skb, hdr_len +
>> sizeof(struct tcphdr)) reads past the end of the skb, triggering a
>> KASAN fault or page fault.
>>
>> Fix this by clearing skb->transport_header to the ~0U sentinel at the
>> tunnel decapsulation boundary, after each tunnel receive function has
>> finished all processing that needs the outer L4 header and before the skb
>> is handed to GRO or the stack. The IP/GRO receive paths then set the
>> transport header correctly when they parse the inner packet.
>>
>> Fixes: 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()")
>
> This Fixes: tag seems pretty random to me. Issue was present long
> before that commit.
> Next time, please be precise with the `Fixes:` tag; it's not decoration.
>
Thanks for your review. Sorry for the late reply.
The stale transport header has been present since 1da177e4c3f4
("Linux-2.6.12-rc2"), which is completely latent until
7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()"
was merged.
From the perspective of the root cause introduced by the issue,
we can use 1da177e4c3f4 ("Linux-2.6.12-rc2") as fixtag.
> Also, calling skb_unset_transport_header(skb) directly from
> gro_cells_receive() would make more sense and
> your patch would be less intrusive.
>
gtp,sit use netif_rx to receive pkt. so modifying only gro_cells_receive
cannot solve all the problems. netif_rx is widely
used and is not only invoked by the tunnel driver, and serval path
(such as genve encap) will reset transport header before netif_rx.
Therefore, should we retain unset in the GTP/SIT tunnel?
Best Regards
Dong Chenchen
> pw-bot: cr
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation
2026-08-20 8:58 ` dongchenchen (A)
@ 2026-08-20 9:16 ` Eric Dumazet
2026-08-20 10:04 ` dongchenchen (A)
0 siblings, 1 reply; 6+ messages in thread
From: Eric Dumazet @ 2026-08-20 9:16 UTC (permalink / raw)
To: dongchenchen (A)
Cc: davem, pabeni, kuba, horms, herbert, kuniyu, idosch,
andrew+netdev, ap420073, steffen.klassert, laforge, jiayuan.chen,
jhs, zhangchangzhong, netdev, syzbot+83181a31faf9455499c5
On Thu, Aug 20, 2026 at 10:58 AM dongchenchen (A)
<dongchenchen2@huawei.com> wrote:
>
>
> > On Thu, Aug 13, 2026 at 5:30 AM Dong Chenchen <dongchenchen2@huawei.com> wrote:
> >>
> >> Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale
> >> transport_header offset after tunnel decapsulation.
> >>
> >> BUG: unable to handle page fault for address: ffffed102091a42e
> >> Oops: Oops: 0000 [#1] SMP KASAN NOPTI
> >> CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full)
> >> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> >> RIP: 0010:__asan_load2
> >> <IRQ>
> >> qdisc_pkt_len_segs_init (net/core/dev.c:4145)
> >> __dev_queue_xmit (net/core/dev.c:4787)
> >> br_dev_queue_push_xmit (net/bridge/br_forward.c:53)
> >> br_handle_frame_finish (net/bridge/br_input.c:229)
> >> br_handle_frame (net/bridge/br_input.c:315)
> >> __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099)
> >> __netif_receive_skb_list_core (net/core/dev.c:6287)
> >> netif_receive_skb_list_internal (net/core/dev.c:6445)
> >> napi_complete_done (net/core/dev.c:6813)
> >> gro_cell_poll (net/core/gro_cells.c:74)
> >> __napi_poll (net/core/dev.c:7735)
> >> net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955)
> >> handle_softirqs (kernel/softirq.c:622)
> >> do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 )
> >> __local_bh_enable_ip (kernel/softirq.c:450)
> >> tun_get_user (drivers/net/tun.c:1986 (discriminator 1))
> >> tun_chr_write_iter (drivers/net/tun.c:2032)
> >>
> >> The crash requires four conditions to line up:
> >>
> >> 1. The incoming packet is encapsulated and carries GSO metadata. The outer
> >> transport header offset is stored in skb->transport_header while the
> >> packet is still in the outer tunnel context.
> >> 2. The tunnel receiver strips the outer headers. skb->data is advanced to
> >> the inner frame, but skb->transport_header is left pointing to the
> >> now-removed outer L4 header, so it becomes a negative offset relative to
> >> the new data.
> >> 3. The inner frame is not delivered to the local IP stack. Instead, it
> >> is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and
> >> the transport header is not reset to the inner L4 offset.
> >> 4. The forwarding path calls __dev_queue_xmit(), which enters
> >> qdisc_pkt_len_segs_init(). That function computes the GSO header length
> >> from skb_transport_offset(skb). Because the offset is negative, the
> >> unsigned cast overflows and pskb_may_pull(skb, hdr_len +
> >> sizeof(struct tcphdr)) reads past the end of the skb, triggering a
> >> KASAN fault or page fault.
> >>
> >> Fix this by clearing skb->transport_header to the ~0U sentinel at the
> >> tunnel decapsulation boundary, after each tunnel receive function has
> >> finished all processing that needs the outer L4 header and before the skb
> >> is handed to GRO or the stack. The IP/GRO receive paths then set the
> >> transport header correctly when they parse the inner packet.
> >>
> >> Fixes: 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()")
> >
> > This Fixes: tag seems pretty random to me. Issue was present long
> > before that commit.
> > Next time, please be precise with the `Fixes:` tag; it's not decoration.
> >
> Thanks for your review. Sorry for the late reply.
> The stale transport header has been present since 1da177e4c3f4
> ("Linux-2.6.12-rc2"), which is completely latent until
> 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()"
> was merged.
>
> From the perspective of the root cause introduced by the issue,
> we can use 1da177e4c3f4 ("Linux-2.6.12-rc2") as fixtag.
>
> > Also, calling skb_unset_transport_header(skb) directly from
> > gro_cells_receive() would make more sense and
> > your patch would be less intrusive.
> >
>
> gtp,sit use netif_rx to receive pkt. so modifying only gro_cells_receive
> cannot solve all the problems. netif_rx is widely
> used and is not only invoked by the tunnel driver, and serval path
> (such as genve encap) will reset transport header before netif_rx.
> Therefore, should we retain unset in the GTP/SIT tunnel?
Hi Dong,
1) Regarding SIT:
SIT decapsulates IPv6 over IPv4 (IPPROTO_IPV6). There is no outer L4 transport
header (no UDP/TCP), so skb->transport_header is not pointing to an
outer L4 header.
2) Regarding GTP:
GTP is an L3-only point-to-point device (ARPHRD_NONE). It cannot be
enslaved by a bridge
or forwarded at L2; its packets always go through ip_rcv()/ip6_rcv()
where the headers
are properly parsed/reset,
3) Regarding Geneve:
When skb->encapsulation is set, inner headers are already parsed and
qdisc_pkt_len_segs_init()
uses skb_inner_transport_offset(skb), not skb_transport_offset(skb).
When skb->encapsulation is 0, it calls gro_cells_receive().
All L2 tunnel drivers that can be bridged/forwarded at L2 (vxlan,
geneve, bareudp, amt, etc.)
use gro_cells_receive().
IMO calling skb_unset_transport_header() directly in gro_cells_receive()
is clean and fixes all of them in one place.
Thanks.
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation
2026-08-20 9:16 ` Eric Dumazet
@ 2026-08-20 10:04 ` dongchenchen (A)
0 siblings, 0 replies; 6+ messages in thread
From: dongchenchen (A) @ 2026-08-20 10:04 UTC (permalink / raw)
To: Eric Dumazet
Cc: davem, pabeni, kuba, horms, herbert, kuniyu, idosch,
andrew+netdev, ap420073, steffen.klassert, laforge, jiayuan.chen,
jhs, zhangchangzhong, netdev, syzbot+83181a31faf9455499c5
在 2026/8/20 17:16, Eric Dumazet 写道:
> On Thu, Aug 20, 2026 at 10:58 AM dongchenchen (A)
> <dongchenchen2@huawei.com> wrote:
>>
>>
>>> On Thu, Aug 13, 2026 at 5:30 AM Dong Chenchen <dongchenchen2@huawei.com> wrote:
>>>>
>>>> Syzbot reported a crash in qdisc_pkt_len_segs_init() caused by a stale
>>>> transport_header offset after tunnel decapsulation.
>>>>
>>>> BUG: unable to handle page fault for address: ffffed102091a42e
>>>> Oops: Oops: 0000 [#1] SMP KASAN NOPTI
>>>> CPU: 0 UID: 0 PID: 340 Comm: qdisc_uaf_repro Not tainted 7.2.0-rc4-00061-g248951ddc14d #256 PREEMPT(full)
>>>> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
>>>> RIP: 0010:__asan_load2
>>>> <IRQ>
>>>> qdisc_pkt_len_segs_init (net/core/dev.c:4145)
>>>> __dev_queue_xmit (net/core/dev.c:4787)
>>>> br_dev_queue_push_xmit (net/bridge/br_forward.c:53)
>>>> br_handle_frame_finish (net/bridge/br_input.c:229)
>>>> br_handle_frame (net/bridge/br_input.c:315)
>>>> __netif_receive_skb_core.constprop.0 (net/core/dev.c:6099)
>>>> __netif_receive_skb_list_core (net/core/dev.c:6287)
>>>> netif_receive_skb_list_internal (net/core/dev.c:6445)
>>>> napi_complete_done (net/core/dev.c:6813)
>>>> gro_cell_poll (net/core/gro_cells.c:74)
>>>> __napi_poll (net/core/dev.c:7735)
>>>> net_rx_action (net/core/dev.c:7798 net/core/dev.c:7955)
>>>> handle_softirqs (kernel/softirq.c:622)
>>>> do_softirq (kernel/softirq.c:523 kernel/softirq.c:510 )
>>>> __local_bh_enable_ip (kernel/softirq.c:450)
>>>> tun_get_user (drivers/net/tun.c:1986 (discriminator 1))
>>>> tun_chr_write_iter (drivers/net/tun.c:2032)
>>>>
>>>> The crash requires four conditions to line up:
>>>>
>>>> 1. The incoming packet is encapsulated and carries GSO metadata. The outer
>>>> transport header offset is stored in skb->transport_header while the
>>>> packet is still in the outer tunnel context.
>>>> 2. The tunnel receiver strips the outer headers. skb->data is advanced to
>>>> the inner frame, but skb->transport_header is left pointing to the
>>>> now-removed outer L4 header, so it becomes a negative offset relative to
>>>> the new data.
>>>> 3. The inner frame is not delivered to the local IP stack. Instead, it
>>>> is forwarded at L2 by a bridge or HSR, so ip_rcv_core() never runs and
>>>> the transport header is not reset to the inner L4 offset.
>>>> 4. The forwarding path calls __dev_queue_xmit(), which enters
>>>> qdisc_pkt_len_segs_init(). That function computes the GSO header length
>>>> from skb_transport_offset(skb). Because the offset is negative, the
>>>> unsigned cast overflows and pskb_may_pull(skb, hdr_len +
>>>> sizeof(struct tcphdr)) reads past the end of the skb, triggering a
>>>> KASAN fault or page fault.
>>>>
>>>> Fix this by clearing skb->transport_header to the ~0U sentinel at the
>>>> tunnel decapsulation boundary, after each tunnel receive function has
>>>> finished all processing that needs the outer L4 header and before the skb
>>>> is handed to GRO or the stack. The IP/GRO receive paths then set the
>>>> transport header correctly when they parse the inner packet.
>>>>
>>>> Fixes: 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()")
>>>
>>> This Fixes: tag seems pretty random to me. Issue was present long
>>> before that commit.
>>> Next time, please be precise with the `Fixes:` tag; it's not decoration.
>>>
>> Thanks for your review. Sorry for the late reply.
>> The stale transport header has been present since 1da177e4c3f4
>> ("Linux-2.6.12-rc2"), which is completely latent until
>> 7fb4c1967011 ("net: pull headers in qdisc_pkt_len_segs_init()"
>> was merged.
>>
>> From the perspective of the root cause introduced by the issue,
>> we can use 1da177e4c3f4 ("Linux-2.6.12-rc2") as fixtag.
>>
>>> Also, calling skb_unset_transport_header(skb) directly from
>>> gro_cells_receive() would make more sense and
>>> your patch would be less intrusive.
>>>
>>
>> gtp,sit use netif_rx to receive pkt. so modifying only gro_cells_receive
>> cannot solve all the problems. netif_rx is widely
>> used and is not only invoked by the tunnel driver, and serval path
>> (such as genve encap) will reset transport header before netif_rx.
>> Therefore, should we retain unset in the GTP/SIT tunnel?
>
> Hi Dong,
>
> 1) Regarding SIT:
>
> SIT decapsulates IPv6 over IPv4 (IPPROTO_IPV6). There is no outer L4 transport
> header (no UDP/TCP), so skb->transport_header is not pointing to an
> outer L4 header.
>
> 2) Regarding GTP:
>
> GTP is an L3-only point-to-point device (ARPHRD_NONE). It cannot be
> enslaved by a bridge
> or forwarded at L2; its packets always go through ip_rcv()/ip6_rcv()
> where the headers
> are properly parsed/reset,
>
Hi, Eric. Thanks for the review!
I missed the rcv path in the commit message.
With an ingress qdisc attached to gtp, the stale transport header
will been consumed in sch_handle_ingress() and its not covered by
gro_cells_receive() either. So maybe gtp is not safe by
construction.
gtp_encap_rcv
gtp_rx //stale transport header
__netif_rx
__netif_receive_skb_core
sch_handle_ingress
qdisc_pkt_len_segs_init
Best Regards
Dong Chenchen
> 3) Regarding Geneve:
>
> When skb->encapsulation is set, inner headers are already parsed and
> qdisc_pkt_len_segs_init()
> uses skb_inner_transport_offset(skb), not skb_transport_offset(skb).
>
> When skb->encapsulation is 0, it calls gro_cells_receive().
>
> All L2 tunnel drivers that can be bridged/forwarded at L2 (vxlan,
> geneve, bareudp, amt, etc.)
> use gro_cells_receive().
>
> IMO calling skb_unset_transport_header() directly in gro_cells_receive()
> is clean and fixes all of them in one place.
>
> Thanks.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-20 10:04 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 3:38 [PATCH net] net: iptunnel: fix stale transport header during tunnel decapsulation Dong Chenchen
2026-08-13 4:39 ` Eric Dumazet
2026-08-19 13:11 ` Eric Dumazet
2026-08-20 8:58 ` dongchenchen (A)
2026-08-20 9:16 ` Eric Dumazet
2026-08-20 10:04 ` dongchenchen (A)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox