mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v3] seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation
@ 2026-10-21  2:47 Boyan Liu
  2026-10-22  5:19 ` Hangbin Liu
  2026-10-23  2:49 ` netdev-bot+sashiko
  0 siblings, 2 replies; 3+ messages in thread
From: Boyan Liu @ 2026-10-21  2:47 UTC (permalink / raw)
  To: Andrea Mayer, David Ahern, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Willem de Bruijn
  Cc: netdev, linux-kernel, co+98d7de09cd94fec5

SRv6 End.DT4/End.DT6 VRF decapsulation unsets the mac header in
end_dt_vrf_rcv() so that the VRF device can rebuild it for packet
sniffers. The VRF rx handler only performs the rebuild when a
sniffer is actually attached to the VRF device (ptype_all
non-empty); otherwise the packet continues up the stack with
mac_header == 0xffff.

Every other receive path delivers packets with a valid mac header:
NIC drivers set it in eth_type_trans(), and tunnels without an L2
header (ipip, sit, gre, tun) reset it to the network header after
decapsulation. skb_mac_header() even warns on the unset state with
DEBUG_NET_WARN_ON_ONCE().

If the decapsulated packet is an unexpected UDP GSO packet,
udp_queue_rcv_skb() re-segments it after restoring skb->data to the
mac header with __skb_push(skb, -skb_mac_offset(skb)). With the mac
header unset, skb_mac_offset() 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)

Restore a valid mac header once the VRF rx handler returns: point
it at the network header, and reset mac_len, which still holds the
ingress frame's L2 header length. 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.

Fixes: 664d6f86868b ("seg6: add support for the SRv6 End.DT4 behavior")
Cc: stable@vger.kernel.org
Reported-by: co+98d7de09cd94fec5@bugs.sh
Signed-off-by: Boyan Liu <yymhvert@gmail.com>
---
v3:
- Also reset skb->mac_len with skb_reset_mac_len(), as suggested by
  Andrea Mayer: it still held the ingress frame's L2 header length.
v2: https://lore.kernel.org/netdev/20261017120144.3059-1-yymhvert@gmail.com/
- Move the fix from the UDP GSO path to seg6local.
- Restore the mac header after the VRF rx handler returns, preserving
  the VRF sniffer behavior.
- Verify the original reproducer, datagram delivery, and VRF sniffer
  output under QEMU.
v1: https://lore.kernel.org/netdev/20261010073015.7665-1-yymhvert@gmail.com/

 net/ipv6/seg6_local.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
index 7b5212220185..4ef9d32907e5 100644
--- a/net/ipv6/seg6_local.c
+++ b/net/ipv6/seg6_local.c
@@ -1113,6 +1113,16 @@ static struct sk_buff *end_dt_vrf_rcv(struct sk_buff *skb, u16 family,
 		/* the skb buffer was consumed by the handler */
 		return NULL;
 
+	/* the VRF device had its chance to rebuild the mac header for
+	 * sniffers; restore a valid one pointing at the network header,
+	 * like any L3 device without an L2 header, before the packet
+	 * continues up the stack.
+	 */
+	if (!skb_mac_header_was_set(skb)) {
+		skb_reset_mac_header(skb);
+		skb_reset_mac_len(skb);
+	}
+
 	/* when a packet is received by a VRF or by one of its slaves, the
 	 * master device reference is set into the skb.
 	 */
-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH net v3] seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation
  2026-10-21  2:47 [PATCH net v3] seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation Boyan Liu
@ 2026-10-22  5:19 ` Hangbin Liu
  2026-10-23  2:49 ` netdev-bot+sashiko
  1 sibling, 0 replies; 3+ messages in thread
From: Hangbin Liu @ 2026-10-22  5:19 UTC (permalink / raw)
  To: Boyan Liu
  Cc: Andrea Mayer, David Ahern, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Willem de Bruijn,
	netdev, linux-kernel, co+98d7de09cd94fec5

On Wed, Oct 21, 2026 at 10:47:38AM +0800, Boyan Liu wrote:
> SRv6 End.DT4/End.DT6 VRF decapsulation unsets the mac header in
> end_dt_vrf_rcv() so that the VRF device can rebuild it for packet
> sniffers. The VRF rx handler only performs the rebuild when a
> sniffer is actually attached to the VRF device (ptype_all
> non-empty); otherwise the packet continues up the stack with
> mac_header == 0xffff.
> 
> Every other receive path delivers packets with a valid mac header:
> NIC drivers set it in eth_type_trans(), and tunnels without an L2
> header (ipip, sit, gre, tun) reset it to the network header after
> decapsulation. skb_mac_header() even warns on the unset state with
> DEBUG_NET_WARN_ON_ONCE().
> 
> If the decapsulated packet is an unexpected UDP GSO packet,
> udp_queue_rcv_skb() re-segments it after restoring skb->data to the
> mac header with __skb_push(skb, -skb_mac_offset(skb)). With the mac
> header unset, skb_mac_offset() 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)
> 
> Restore a valid mac header once the VRF rx handler returns: point
> it at the network header, and reset mac_len, which still holds the
> ingress frame's L2 header length. 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.
> 
> Fixes: 664d6f86868b ("seg6: add support for the SRv6 End.DT4 behavior")
> Cc: stable@vger.kernel.org
> Reported-by: co+98d7de09cd94fec5@bugs.sh
> Signed-off-by: Boyan Liu <yymhvert@gmail.com>
> ---
> v3:
> - Also reset skb->mac_len with skb_reset_mac_len(), as suggested by
>   Andrea Mayer: it still held the ingress frame's L2 header length.
> v2: https://lore.kernel.org/netdev/20261017120144.3059-1-yymhvert@gmail.com/
> - Move the fix from the UDP GSO path to seg6local.
> - Restore the mac header after the VRF rx handler returns, preserving
>   the VRF sniffer behavior.
> - Verify the original reproducer, datagram delivery, and VRF sniffer
>   output under QEMU.
> v1: https://lore.kernel.org/netdev/20261010073015.7665-1-yymhvert@gmail.com/
> 
>  net/ipv6/seg6_local.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
> index 7b5212220185..4ef9d32907e5 100644
> --- a/net/ipv6/seg6_local.c
> +++ b/net/ipv6/seg6_local.c
> @@ -1113,6 +1113,16 @@ static struct sk_buff *end_dt_vrf_rcv(struct sk_buff *skb, u16 family,
>  		/* the skb buffer was consumed by the handler */
>  		return NULL;
>  
> +	/* the VRF device had its chance to rebuild the mac header for
> +	 * sniffers; restore a valid one pointing at the network header,
> +	 * like any L3 device without an L2 header, before the packet
> +	 * continues up the stack.
> +	 */
> +	if (!skb_mac_header_was_set(skb)) {
> +		skb_reset_mac_header(skb);
> +		skb_reset_mac_len(skb);
> +	}
> +
>  	/* when a packet is received by a VRF or by one of its slaves, the
>  	 * master device reference is set into the skb.
>  	 */
> -- 
> 2.43.0
> 

Thanks for the fix.

Reviewed-by: Hangbin Liu <liuhangbin@kylinos.cn>

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH net v3] seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation
  2026-10-21  2:47 [PATCH net v3] seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation Boyan Liu
  2026-10-22  5:19 ` Hangbin Liu
@ 2026-10-23  2:49 ` netdev-bot+sashiko
  1 sibling, 0 replies; 3+ messages in thread
From: netdev-bot+sashiko @ 2026-10-23  2:49 UTC (permalink / raw)
  To: yymhvert
  Cc: andrea.mayer, dsahern, davem, edumazet, kuba, pabeni, horms,
	willemdebruijn.kernel, netdev, linux-kernel, co+98d7de09cd94fec5

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 0 (1)

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The patch restores the mac header only after the VRF
  l3mdev_l3_rcv handler returns.

Pre-existing issues:
- [Low] Concern: the other SRv6 decap behaviours (End.DX4/DX6, End.DT6
  legacy, End.DT46 non-VRF) leave mac_header and mac_len pointing at the…

--

seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation

After the VRF l3mdev_l3_rcv handler returns, this commit restores a valid
mac header in end_dt_vrf_rcv() if the handler did not rebuild one. It
points the mac header at the network header and resets mac_len. With
that, UDP GSO re-segmentation no longer does a huge __skb_push() from an
unset mac header.

> Every other receive path delivers packets with a valid mac header:
> NIC drivers set it in eth_type_trans(), and tunnels without an L2
> header (ipip, sit, gre, tun) reset it to the network header after
> decapsulation.

[Severity: Low]
This isn't a bug introduced by this patch, but is this description
accurate for ipip, sit and gre?

In ip_tunnel_rcv() in net/ipv4/ip_tunnel.c, only the ARPHRD_ETHER case
touches the mac header:

	if (tunnel->dev->type == ARPHRD_ETHER) {
		skb->protocol = eth_type_trans(skb, tunnel->dev);
		skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
	} else {
		skb->dev = tunnel->dev;
	}

In the L3 tunnel case the outer mac header stays set. It is not reset to
the network header.

The non-VRF SRv6 decap behaviours (End.DX4/DX6, legacy End.DT6, non-VRF
End.DT46) end up in the same state after decap_and_validate(), which only
resets the network and transport headers.

That stale but set state looks harmless at runtime. __skb_gso_segment()
calls skb_reset_mac_header() and skb_reset_mac_len() before
skb_mac_gso_segment(), and the outer mac header is still inside the
buffer.

Could the commit message wording be adjusted, though?

> diff --git a/net/ipv6/seg6_local.c b/net/ipv6/seg6_local.c
> index 53f7b24525759..45d0aabc58152 100644
> --- a/net/ipv6/seg6_local.c
> +++ b/net/ipv6/seg6_local.c
> @@ -1200,6 +1200,16 @@ static struct sk_buff *end_dt_vrf_rcv(struct sk_buff *skb, u16 family,
>  		/* the skb buffer was consumed by the handler */
>  		return NULL;
>  
> +	/* the VRF device had its chance to rebuild the mac header for
> +	 * sniffers; restore a valid one pointing at the network header,
> +	 * like any L3 device without an L2 header, before the packet
> +	 * continues up the stack.
> +	 */
> +	if (!skb_mac_header_was_set(skb)) {
> +		skb_reset_mac_header(skb);
> +		skb_reset_mac_len(skb);
> +	}

[Severity: High]
Does this restore happen early enough? end_dt_vrf_rcv() calls
skb_unset_mac_header() before l3mdev_l3_rcv(). If no sniffer is attached
to the VRF device, the header is still unset when the PRE_ROUTING hook
runs inside the handler:

drivers/net/vrf.c:vrf_ip_rcv() {
    ...
	if (!list_empty(&vrf_dev->ptype_all)) {
		...
		err = vrf_add_mac_header_if_unset(skb, vrf_dev, ETH_P_IP,
						  orig_dev);
		...
	}

	skb = vrf_rcv_nfhook(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, vrf_dev);
    ...
}

vrf_ip6_rcv() is structured the same way.

Suppose an NFQUEUE rule queues a GSO packet at that hook, either on a
queue without NFQA_CFG_F_GSO or as an SCTP GSO packet.
nfqnl_enqueue_packet() then does:

	nf_bridge_adjust_skb_data(skb);
	segs = skb_gso_segment(skb, 0);

and nf_bridge_adjust_skb_data() does:

	if (nf_bridge_info_get(skb))
		__skb_push(skb, skb_mac_header_len(skb));

With mac_header == 0xffff, skb_mac_header_len() is network_header - 0xffff,
which is roughly 4G as a u32.

Can that move skb->data far outside the buffer and corrupt skb->len before
skb_gso_segment() reads through it?

The bridge netfilter extension can still be attached at this point. An
IPv6+SRH packet that arrives on a bridge port with br_netfilter and
call-ip6tables enabled gets it from nf_bridge_alloc() in
br_nf_pre_routing_ipv6().

Nothing on this path drops it. br_pass_frame_up(), decap_and_validate(),
iptunnel_pull_offloads() and nf_reset_ct() all leave it in place.
nf_bridge_info_free() is only called on the bridge xmit paths.

ip_sabotage_in() also lets the packet through PRE_ROUTING on the VRF
device. sabotage_in_done was already set during the first PRE_ROUTING
pass on the bridge device:

	if (nf_bridge) {
		if (nf_bridge->sabotage_in_done)
			return NF_ACCEPT;

		if (!nf_bridge->in_prerouting &&
		    !netif_is_l3_master(skb->dev) &&
		    !netif_is_l3_slave(skb->dev)) {

The call chain would be:

ipv6_rcv()
  seg6_local_input_core()
    end_dt_vrf_rcv()
      skb_unset_mac_header()
      vrf_ip_rcv()
        vrf_rcv_nfhook(NF_INET_PRE_ROUTING)
          nfqnl_enqueue_packet()
            nf_bridge_adjust_skb_data()
              __skb_push(skb, huge)
            skb_gso_segment()

This looks like the same problem: an unset End.DT VRF mac header becomes a
huge unsigned __skb_push() before GSO. The difference is that it happens
inside the handler rather than after it returns.

Would it make sense to set a valid mac header before calling
l3mdev_l3_rcv()? Alternatively, the consumers inside the handler could be
made to handle the unset state.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261021024738.17266-1-yymhvert%40gmail.com

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-23  2:49 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-21  2:47 [PATCH net v3] seg6: Fix unset mac header in SRv6 End.DT VRF decapsulation Boyan Liu
2026-10-22  5:19 ` Hangbin Liu
2026-10-23  2:49 ` netdev-bot+sashiko

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®