* [PATCH net] udp: Fix out-of-bounds read with unset mac header in unexpected GSO path
@ 2026-10-10 7:30 Boyan Liu
2026-10-15 2:49 ` Willem de Bruijn
0 siblings, 1 reply; 3+ messages in thread
From: Boyan Liu @ 2026-10-10 7:30 UTC (permalink / raw)
To: Willem de Bruijn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman
Cc: netdev, linux-kernel, co+98d7de09cd94fec5, Boyan Liu, stable
udp_queue_rcv_skb() and udpv6_queue_rcv_skb() re-segment an
unexpected GSO packet after restoring skb->data to the mac
header with __skb_push(skb, -skb_mac_offset(skb)), assuming
the mac header is valid.
SRv6 End.DT4/End.DT6 VRF decapsulation calls
skb_unset_mac_header(). iptunnel_pull_offloads() can leave an
inner UDP GSO type (SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST)
behind, so a socket that does not accept that GRO type runs
the unexpected GSO path with mac_header == 0xffff.
skb_mac_offset() then returns a large positive value, which
the unsigned __skb_push() argument turns into a huge length:
skb->data moves far outside the buffer and skb->len is
corrupted, so segmentation reads out of bounds:
BUG: KASAN: slab-out-of-bounds in inet_gso_segment (net/ipv4/af_inet.c:1383)
Read of size 1 at addr ffff88800da7ffff by task exploit/149
Call Trace:
inet_gso_segment (net/ipv4/af_inet.c:1383)
skb_mac_gso_segment (net/core/gso.c:53)
__skb_gso_segment (net/core/gso.c:124)
udp_queue_rcv_skb (net/ipv4/udp.c:2476 net/ipv4/udp.c:2466)
udp_unicast_rcv_skb (net/ipv4/udp.c:2620)
ip_local_deliver (net/ipv4/ip_input.c:262)
seg6_local_input_core (net/ipv6/seg6_local.c:1640)
lwtunnel_input (net/core/lwtunnel.c:465)
ipv6_rcv (net/ipv6/ip6_input.c:351)
__skb_gso_segment() resets the mac header to wherever
skb->data points, so the push target only needs to be a valid
header start. Fall back to the network header when the mac
header was never set.
Fixes: cf329aa42b66 ("udp: cope with UDP GRO packet misdirection")
Cc: stable@vger.kernel.org
Reported-by: co+98d7de09cd94fec5@bugs.sh
Signed-off-by: Boyan Liu <yymhvert@gmail.com>
---
net/ipv4/udp.c | 5 ++++-
net/ipv6/udp.c | 5 ++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index bb8cfc62c..fe5a23f1c 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -2441,7 +2441,10 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
return udp_queue_rcv_one_skb(sk, skb);
BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
- __skb_push(skb, -skb_mac_offset(skb));
+ if (skb_mac_header_was_set(skb))
+ __skb_push(skb, -skb_mac_offset(skb));
+ else
+ __skb_push(skb, -skb_network_offset(skb));
segs = udp_rcv_segment(sk, skb, true);
skb_list_walk_safe(segs, skb, next) {
__skb_pull(skb, skb_transport_offset(skb));
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 93478d1ad..8db4e2869 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -895,7 +895,10 @@ static int udpv6_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
if (likely(!udp_unexpected_gso(sk, skb)))
return udpv6_queue_rcv_one_skb(sk, skb);
- __skb_push(skb, -skb_mac_offset(skb));
+ if (skb_mac_header_was_set(skb))
+ __skb_push(skb, -skb_mac_offset(skb));
+ else
+ __skb_push(skb, -skb_network_offset(skb));
segs = udp_rcv_segment(sk, skb, false);
skb_list_walk_safe(segs, skb, next) {
__skb_pull(skb, skb_transport_offset(skb));
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net] udp: Fix out-of-bounds read with unset mac header in unexpected GSO path
2026-10-10 7:30 [PATCH net] udp: Fix out-of-bounds read with unset mac header in unexpected GSO path Boyan Liu
@ 2026-10-15 2:49 ` Willem de Bruijn
2026-10-16 9:05 ` Boyan Liu
0 siblings, 1 reply; 3+ messages in thread
From: Willem de Bruijn @ 2026-10-15 2:49 UTC (permalink / raw)
To: Boyan Liu, Willem de Bruijn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman
Cc: netdev, linux-kernel, co+98d7de09cd94fec5, Boyan Liu, stable
Boyan Liu wrote:
> udp_queue_rcv_skb() and udpv6_queue_rcv_skb() re-segment an
> unexpected GSO packet after restoring skb->data to the mac
> header with __skb_push(skb, -skb_mac_offset(skb)), assuming
> the mac header is valid.
>
> SRv6 End.DT4/End.DT6 VRF decapsulation calls
> skb_unset_mac_header(). iptunnel_pull_offloads() can leave an
> inner UDP GSO type (SKB_GSO_UDP_L4 or SKB_GSO_FRAGLIST)
> behind, so a socket that does not accept that GRO type runs
> the unexpected GSO path with mac_header == 0xffff.
> skb_mac_offset() then returns a large positive value, which
> the unsigned __skb_push() argument turns into a huge length:
> skb->data moves far outside the buffer and skb->len is
> corrupted, so segmentation reads out of bounds:
>
> BUG: KASAN: slab-out-of-bounds in inet_gso_segment (net/ipv4/af_inet.c:1383)
> Read of size 1 at addr ffff88800da7ffff by task exploit/149
> Call Trace:
> inet_gso_segment (net/ipv4/af_inet.c:1383)
> skb_mac_gso_segment (net/core/gso.c:53)
> __skb_gso_segment (net/core/gso.c:124)
> udp_queue_rcv_skb (net/ipv4/udp.c:2476 net/ipv4/udp.c:2466)
> udp_unicast_rcv_skb (net/ipv4/udp.c:2620)
> ip_local_deliver (net/ipv4/ip_input.c:262)
> seg6_local_input_core (net/ipv6/seg6_local.c:1640)
> lwtunnel_input (net/core/lwtunnel.c:465)
> ipv6_rcv (net/ipv6/ip6_input.c:351)
>
> __skb_gso_segment() resets the mac header to wherever
> skb->data points, so the push target only needs to be a valid
> header start. Fall back to the network header when the mac
> header was never set.
I wonder if the right fix here is to not call skb_unset_mac_header in
the first place.
There are plenty of L3 device type with no L2 header. They still have
a valid skb_mac_header.
This fix may be papering over a tunnel doing something non-standard.
> Fixes: cf329aa42b66 ("udp: cope with UDP GRO packet misdirection")
> Cc: stable@vger.kernel.org
> Reported-by: co+98d7de09cd94fec5@bugs.sh
> Signed-off-by: Boyan Liu <yymhvert@gmail.com>
> ---
> net/ipv4/udp.c | 5 ++++-
> net/ipv6/udp.c | 5 ++++-
> 2 files changed, 8 insertions(+), 2 deletions(-)
>
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index bb8cfc62c..fe5a23f1c 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -2441,7 +2441,10 @@ static int udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
> return udp_queue_rcv_one_skb(sk, skb);
>
> BUILD_BUG_ON(sizeof(struct udp_skb_cb) > SKB_GSO_CB_OFFSET);
> - __skb_push(skb, -skb_mac_offset(skb));
> + if (skb_mac_header_was_set(skb))
> + __skb_push(skb, -skb_mac_offset(skb));
> + else
> + __skb_push(skb, -skb_network_offset(skb));
> segs = udp_rcv_segment(sk, skb, true);
> skb_list_walk_safe(segs, skb, next) {
> __skb_pull(skb, skb_transport_offset(skb));
> diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
> index 93478d1ad..8db4e2869 100644
> --- a/net/ipv6/udp.c
> +++ b/net/ipv6/udp.c
> @@ -895,7 +895,10 @@ static int udpv6_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
> if (likely(!udp_unexpected_gso(sk, skb)))
> return udpv6_queue_rcv_one_skb(sk, skb);
>
> - __skb_push(skb, -skb_mac_offset(skb));
> + if (skb_mac_header_was_set(skb))
> + __skb_push(skb, -skb_mac_offset(skb));
> + else
> + __skb_push(skb, -skb_network_offset(skb));
> segs = udp_rcv_segment(sk, skb, false);
> skb_list_walk_safe(segs, skb, next) {
> __skb_pull(skb, skb_transport_offset(skb));
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net] udp: Fix out-of-bounds read with unset mac header in unexpected GSO path
2026-10-15 2:49 ` Willem de Bruijn
@ 2026-10-16 9:05 ` Boyan Liu
0 siblings, 0 replies; 3+ messages in thread
From: Boyan Liu @ 2026-10-16 9:05 UTC (permalink / raw)
To: Willem de Bruijn
Cc: David S . Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Jakub Kicinski, netdev, linux-kernel, stable,
co+98d7de09cd94fec5
Hi Willem,
On Thu, Oct 15, 2026 at 10:49 AM, Willem de Bruijn wrote:
> I wonder if the right fix here is to not call skb_unset_mac_header in
> the first place.
>
> There are plenty of L3 device type with no L2 header. They still have
> a valid skb_mac_header.
>
> This fix may be papering over a tunnel doing something non-standard.
You're right, and after digging into it I agree the seg6local side is
the real offender.
I checked every skb_unset_mac_header() call site in the tree. There
are exactly two: net/ipv6/seg6_local.c (end_dt_vrf_rcv) and
net/core/filter.c (BPF egress, unrelated to the rx path). Every other
receive path delivers packets with a valid mac header: NIC drivers set
it in eth_type_trans(), and L3 tunnels without an L2 header (ipip,
sit, gre, tun, loopback) reset it to the network header after
decapsulation. seg6local is the only producer that lets a packet
continue up the stack with mac_header unset, which is also the state
skb_mac_header() warns about with DEBUG_NET_WARN_ON_ONCE().
One complication: the unset in end_dt_vrf_rcv() is not gratuitous.
vrf_l3_rcv() -> vrf_add_mac_header_if_unset() relies on it to
synthesize a fake Ethernet header when a sniffer (e.g. tcpdump) is
attached to the VRF device. Dropping the unset entirely would change
what sniffers see on VRF devices.
So instead of removing it, I restore a valid mac header once the VRF
rx handler returns:
if (!skb_mac_header_was_set(skb))
skb_reset_mac_header(skb);
The sniffer rebuild happens inside the handler while the header is
still unset, so tap behavior is unchanged; when no rebuild happened,
the packet now carries the same mac header state as any packet
received on an L3 device.
Tested under QEMU with the original reproducer:
- unpatched kernel: KASAN slab-out-of-bounds in inet_gso_segment,
same stack as the original report;
- patched kernel: no crash, the re-segmented datagrams are delivered
intact, and the unexpected GSO path is confirmed taken;
- with a sniffer attached to the VRF device, the fake Ethernet header
presented to the tap is identical before and after the patch.
I'll send this as v2 with the fix moved to seg6_local.c. If you'd
rather drop the skb_unset_mac_header() altogether and accept the
sniffer behavior change, I'm happy to do that instead.
Thanks,
Boyan
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-16 9:05 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-10 7:30 [PATCH net] udp: Fix out-of-bounds read with unset mac header in unexpected GSO path Boyan Liu
2026-10-15 2:49 ` Willem de Bruijn
2026-10-16 9:05 ` Boyan Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®