Re: [PATCH net v2] fou: reserve headroom before pushing the encap header

From: Chengfeng Ye

Date: Wed Oct 21 2026 - 05:33:23 EST


On Wed, Oct 21, 2026 at 2:55 PM Theodor Arsenij Larionov Trichkine
<theodorlarionov@xxxxxxxxx> wrote:
>
> ip_tunnel_xmit() and ip_md_tunnel_xmit() call ip_tunnel_encap() before
> reserving headroom. A packet redirected into the tunnel (tc mirred, nft
> fwd) has only the headroom of its original device, so fou_build_udp()
> hits skb_under_panic(); with a mirred mirror the push overwrites the
> Ethernet header of the original packet.
>
> Reserve the headroom in fou_build_header() and gue_build_header(). GUE
> sizes and builds the header from one copy of the encap config, which
> can change concurrently. As the skb head may move, ip_tunnel_xmit() and
> ip_md_tunnel_xmit() reload inner_iph after ip_tunnel_encap(), and
> ip_tunnel_xmit() reads tnl_params before it: for GRE with header_ops,
> tnl_params points into the head.
>
> Fixes: 56328486539d ("net: Changes to ip_tunnel to support foo-over-udp encapsulation")
> Fixes: ac931d4cdec3 ("ipip,ip_tunnel,sit: Add FOU support for externally controlled ipip devices")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Theodor Arsenij Larionov Trichkine <theodorlarionov@xxxxxxxxx>
> ---
> v2: move the headroom reservation into {fou,gue}_build_header(), reload
> inner_iph after ip_tunnel_encap() and read tnl_params before it, which
> the Sashiko review of v1 pointed out.
> v1: https://lore.kernel.org/netdev/20261010223601.1193655-1-theodorlarionov@xxxxxxxxx/
>
> Chengfeng Ye posted an earlier series for this bug; this takes the
> builder approach Paolo suggested.
> Link: https://lore.kernel.org/netdev/20260904165544.1362052-1-nicoyip.dev@xxxxxxxxx/

My v1/v2 [1][2] reserved the headroom in gue_build_header(), close to
what this patch does. Ido objected to that [3]: the caller of
ip_tunnel_encap() is expected to reserve encap_hlen() bytes, and an
skb_cow_head() in build_header() forces every caller to refresh
pointers into skb->head. I moved the fix into ip_tunnel_xmit() for
v3/v4 as Ido suggested, and wait for the IPv6 part [4] to be accepted first.
That patch is changes-requested, and Paolo said the IPv6 side should become
an RCU conversion targeting net-next [5].

I'm fine with either approach, and will drop my v4 if this patch is taken.

If this version is applied, I'd appreciate a Reported-by, as I first
reported the bug in [1]:

Reported-by: Chengfeng Ye <nicoyip.dev@xxxxxxxxx>
Closes: https://lore.kernel.org/netdev/20260801060115.3538849-1-nicoyip.dev@xxxxxxxxx/

[1] https://lore.kernel.org/netdev/20260801060115.3538849-1-nicoyip.dev@xxxxxxxxx/
[2] https://lore.kernel.org/netdev/20260821145908.2829132-1-nicoyip.dev@xxxxxxxxx/
[3] https://lore.kernel.org/netdev/20260824075011.GA858321@shredder/
[4] https://lore.kernel.org/netdev/2ce8f3e4bbed6060afb0aee33dc4e1d9ae021280.1788262122.git.petalzu987@xxxxxxxxx/
[5] https://lore.kernel.org/netdev/e651fe9e-2d84-4320-99a1-6fddcfc00889@xxxxxxxxxx/

> net/ipv4/fou_core.c | 15 +++++++++++----
> net/ipv4/ip_tunnel.c | 6 ++++--
> 2 files changed, 15 insertions(+), 6 deletions(-)
>
> diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c
> index 076fdca44f51..896e63adf698 100644
> --- a/net/ipv4/fou_core.c
> +++ b/net/ipv4/fou_core.c
> @@ -1059,6 +1059,9 @@ static int fou_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
> __be16 sport;
> int err;
>
> + if (skb_cow_head(skb, fou_encap_hlen(e)))
> + return -ENOMEM;
> +
> err = __fou_build_header(skb, e, protocol, &sport, type);
> if (err)
> return err;
> @@ -1071,16 +1074,20 @@ static int fou_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
> static int gue_build_header(struct sk_buff *skb, struct ip_tunnel_encap *e,
> u8 *protocol, struct flowi4 *fl4)
> {
> - int type = e->flags & TUNNEL_ENCAP_FLAG_CSUM ? SKB_GSO_UDP_TUNNEL_CSUM :
> - SKB_GSO_UDP_TUNNEL;
> + struct ip_tunnel_encap encap = READ_ONCE(*e);
> + int type = encap.flags & TUNNEL_ENCAP_FLAG_CSUM ?
> + SKB_GSO_UDP_TUNNEL_CSUM : SKB_GSO_UDP_TUNNEL;
> __be16 sport;
> int err;
>
> - err = __gue_build_header(skb, e, protocol, &sport, type);
> + if (skb_cow_head(skb, gue_encap_hlen(&encap)))
> + return -ENOMEM;
> +
> + err = __gue_build_header(skb, &encap, protocol, &sport, type);
> if (err)
> return err;
>
> - fou_build_udp(skb, e, fl4, protocol, sport);
> + fou_build_udp(skb, &encap, fl4, protocol, sport);
>
> return 0;
> }
> diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
> index e6bcf01411d0..742c2b309608 100644
> --- a/net/ipv4/ip_tunnel.c
> +++ b/net/ipv4/ip_tunnel.c
> @@ -606,6 +606,7 @@ void ip_md_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
>
> if (ip_tunnel_encap(skb, &tun_info->encap, &proto, &fl4) < 0)
> goto tx_error;
> + inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
>
> use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
> if (use_cache)
> @@ -749,6 +750,8 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
> }
>
> tos = tnl_params->tos;
> + df = tnl_params->frag_off;
> + ttl = tnl_params->ttl;
> if (tos & 0x1) {
> tos &= ~0x1;
> if (payload_protocol == htons(ETH_P_IP)) {
> @@ -767,6 +770,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
>
> if (ip_tunnel_encap(skb, &tunnel->encap, &protocol, &fl4) < 0)
> goto tx_error;
> + inner_iph = (const struct iphdr *)skb_inner_network_header(skb);
>
> if (connected && md) {
> use_cache = ip_tunnel_dst_cache_usable(skb, tun_info);
> @@ -799,7 +803,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
> goto tx_error;
> }
>
> - df = tnl_params->frag_off;
> if (payload_protocol == htons(ETH_P_IP) && !tunnel->ignore_df)
> df |= (inner_iph->frag_off & htons(IP_DF));
>
> @@ -821,7 +824,6 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
> }
>
> tos = ip_tunnel_ecn_encap(tos, inner_iph, skb);
> - ttl = tnl_params->ttl;
> if (ttl == 0) {
> if (payload_protocol == htons(ETH_P_IP))
> ttl = inner_iph->ttl;
> --
> 2.34.1
>

Thanks,
Chengfeng