diff options
| author | Paolo Abeni <pabeni@redhat.com> | 2026-08-18 12:42:30 +0200 |
|---|---|---|
| committer | Paolo Abeni <pabeni@redhat.com> | 2026-08-18 12:42:31 +0200 |
| commit | 116e8fe495d811d2a727177a4668e7f79fdffef2 (patch) | |
| tree | d206ea9474975a6e5585fac0532153aa1a26a93d | |
| parent | 8acf691d8017012e1476c30e7381513c1e929c94 (diff) | |
| parent | 6b222adeb9340306e2ff97127c76117abb9b3df8 (diff) | |
Merge branch 'net-fix-ip6gre-header-length-before-capping-tunnel-headroom'
Zhiling Zou says:
====================
net: fix IP6GRE header length before capping tunnel headroom
We found and validated an issue in IP tunnel headroom accounting. The bug is
reachable by a non-root user via user and net namespace.
We've tested it, and it should not affect any other functionality.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
IP tunnel devices derive advertised headroom from lower output devices. A
namespace-local stack of tunnel devices can make that advertised reservation
larger than the 16-bit skb header offsets can represent. Once IP output
reserves that space and records network or transport header offsets, later skb
head expansion can wrap those offsets and leave header helpers pointing into
headroom instead of the packet area.
For IP6GRE, there is an earlier accounting bug that has to be fixed first:
ip6gre_tnl_link_config_route() folds the lower device's hard_header_len into
the tunnel device's hard_header_len whenever header_ops is set. That is wrong
for both header_ops users. ip6gretap and ip6erspan have a fixed Ethernet
hardware header length, while an NBMA ip6gre tunnel's header_ops creates only
the tunnel header: GRE, optional FOU or GUE, and the outer IPv6 header. The
lower device header is needed headroom, not part of the tunnel device's
hardware header.
This series first fixes that IP6GRE hardware-header accounting, then caps the
advertised IP tunnel needed_headroom at the same 512-byte limit already used
by the runtime tunnel transmit path. Tunnel transmit can still expand the skb
when a packet needs more headroom, so nonsensical stacked configurations may
pay an extra reallocation but cannot publish an unbounded reservation to upper
layers.
====================
Link: https://patch.msgid.link/cover.1786542637.git.zhilinz@nebusec.ai
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
| -rw-r--r-- | include/net/ip_tunnels.h | 11 | ||||
| -rw-r--r-- | net/ipv4/ip_tunnel.c | 2 | ||||
| -rw-r--r-- | net/ipv6/ip6_gre.c | 14 | ||||
| -rw-r--r-- | net/ipv6/ip6_tunnel.c | 7 | ||||
| -rw-r--r-- | net/ipv6/sit.c | 2 |
5 files changed, 22 insertions, 14 deletions
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h index d708b66e55cd..85e3455cea25 100644 --- a/include/net/ip_tunnels.h +++ b/include/net/ip_tunnels.h @@ -629,8 +629,7 @@ struct metadata_dst *iptunnel_metadata_reply(struct metadata_dst *md, int skb_tunnel_check_pmtu(struct sk_buff *skb, struct dst_entry *encap_dst, int headroom, bool reply); -static inline void ip_tunnel_adj_headroom(struct net_device *dev, - unsigned int headroom) +static inline unsigned int ip_tunnel_limit_headroom(unsigned int headroom) { /* we must cap headroom to some upperlimit, else pskb_expand_head * will overflow header offsets in skb_headers_offset_update(). @@ -640,6 +639,14 @@ static inline void ip_tunnel_adj_headroom(struct net_device *dev, if (headroom > max_allowed) headroom = max_allowed; + return headroom; +} + +static inline void ip_tunnel_adj_headroom(struct net_device *dev, + unsigned int headroom) +{ + headroom = ip_tunnel_limit_headroom(headroom); + if (headroom > READ_ONCE(dev->needed_headroom)) WRITE_ONCE(dev->needed_headroom, headroom); } diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c index 9d114bd575f9..5b1f180485d4 100644 --- a/net/ipv4/ip_tunnel.c +++ b/net/ipv4/ip_tunnel.c @@ -317,7 +317,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev) mtu = min(tdev->mtu, IP_MAX_MTU); } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); mtu -= t_hlen + (dev->type == ARPHRD_ETHER ? dev->hard_header_len : 0); if (mtu < IPV4_MIN_MTU) diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c index b843116e9b70..200d0ba1a40e 100644 --- a/net/ipv6/ip6_gre.c +++ b/net/ipv6/ip6_gre.c @@ -1137,13 +1137,11 @@ static void ip6gre_tnl_link_config_route(struct ip6_tnl *t, int set_mtu, return; if (rt->dst.dev) { - unsigned short dst_len = rt->dst.dev->hard_header_len + - t_hlen; + unsigned int headroom; - if (t->dev->header_ops) - dev->hard_header_len = dst_len; - else - dev->needed_headroom = dst_len; + headroom = rt->dst.dev->hard_header_len + t_hlen; + headroom = ip_tunnel_limit_headroom(headroom); + dev->needed_headroom = headroom; if (set_mtu) { int mtu = rt->dst.dev->mtu - t_hlen; @@ -1171,8 +1169,8 @@ static int ip6gre_calc_hlen(struct ip6_tnl *tunnel) t_hlen = tunnel->hlen + sizeof(struct ipv6hdr); - if (tunnel->dev->header_ops) - tunnel->dev->hard_header_len = LL_MAX_HEADER + t_hlen; + if (tunnel->dev->header_ops && tunnel->dev->type == ARPHRD_IP6GRE) + tunnel->dev->hard_header_len = t_hlen; else tunnel->dev->needed_headroom = LL_MAX_HEADER + t_hlen; diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 6a1b901ecc9b..cc96bb8b706e 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c @@ -1514,8 +1514,11 @@ static void ip6_tnl_link_config(struct ip6_tnl *t) tdev = __dev_get_by_index(t->net, p->link); if (tdev) { - dev->needed_headroom = tdev->hard_header_len + - tdev->needed_headroom + t_hlen; + unsigned int headroom; + + headroom = tdev->hard_header_len + tdev->needed_headroom; + headroom += t_hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(headroom); mtu = min_t(unsigned int, tdev->mtu, IP6_MAX_MTU); mtu = mtu - t_hlen; diff --git a/net/ipv6/sit.c b/net/ipv6/sit.c index a38b24fb8384..19b7fa8d1a2a 100644 --- a/net/ipv6/sit.c +++ b/net/ipv6/sit.c @@ -1131,7 +1131,7 @@ static void ipip6_tunnel_bind_dev(struct net_device *dev) WRITE_ONCE(dev->mtu, mtu); hlen = tdev->hard_header_len + tdev->needed_headroom; } - dev->needed_headroom = t_hlen + hlen; + dev->needed_headroom = ip_tunnel_limit_headroom(t_hlen + hlen); } static void ipip6_tunnel_update(struct ip_tunnel *t, |
