Re: [PATCH net v3 4/5] ip_gre: recompute erspan header lengths after a change
From: netdev-bot+sashiko@kernel.org
Date: 2026-09-25 06:53:04
Also in:
stable
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 4 · Low: 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). - [Medium] The commit message says "There is no memory safety issue", but the bug being fixed also sends 4 uninitialized bytes of skb headroom… - [Medium] On an existing erspan_ver 0 tunnel, every changelink that carries IFLA_INFO_DATA but omits IFLA_GRE_ERSPAN_VER (and… - [Medium] In a Cc: stable fix, the patch rejects non-zero IFLA_GRE_IFLAGS/IFLA_GRE_OFLAGS for erspan_ver 0 and for… - [Medium] This patch makes tunnel->tun_hlen/hlen change at runtime on a live erspan device (erspan_changelink() -> erspan_link_update() ->… - [Low] erspan_xmit() now always sets IP_TUNNEL_SEQ_BIT for erspan_ver != 0 (line 750), whatever t->parms.o_flags holds. -- ip_gre: recompute erspan header lengths after a change This patch moves the ERSPAN tun_hlen/hlen computation into erspan_set_hlen(). A new erspan_link_update() calls it at the end of erspan_changelink() to refresh the header lengths, headroom and MTU. It also rejects GRE flags for erspan_ver 0 and collect_md. In erspan_xmit(), IP_TUNNEL_SEQ_BIT is now derived from erspan_ver.
There is no memory safety issue: dev->needed_headroom is at least tunnel->hlen + sizeof(struct iphdr), and erspan_xmit() pushes at most 12 + 8 bytes before ip_tunnel_xmit() takes over and cows again.
[Severity: Medium]
Is this statement complete? The bug being fixed also appears to send 4
uninitialized bytes of skb headroom in ERSPAN packets.
In gre_build_header(), the word at greh + hdr_len - 4 is only written
when the SEQ, KEY or CSUM bit is set:
include/net/gre.h:gre_build_header() {
skb_push(skb, hdr_len);
...
if (ip_tunnel_flags_intersect(flags, cond)) {
__be32 *ptr = (__be32 *)(((u8 *)greh) + hdr_len - 4);
...
}
Before this patch, there were two ways to reach hdr_len == 8 with none
of those bits set:
- After a v1/v2 -> v0 changelink, tun_hlen stayed at 8, but
erspan_xmit() cleared the SEQ and KEY bits for v0.
- erspan_validate() only checks that IFLAGS | OFLAGS == GRE_SEQ | GRE_KEY.
That means OFLAGS=KEY with IFLAGS=SEQ|KEY is accepted, and o_flags
then lacks SEQ. erspan_xmit() clears KEY, and __gre_xmit() pushes
8 bytes without writing any optional field.
This patch closes the first path through erspan_link_update(). It closes
the second through the new __assign_bit(IP_TUNNEL_SEQ_BIT, ...), but the
changelog only says that change makes SEQ match erspan_ver.
Since this is Cc: stable, could the changelog say that GRE header bytes
4..7 used to go out on the wire uninitialized?
quoted hunk ↗ jump to hunk
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c index 27b3b4c584b1e..7385d66a94bf4 100644 --- a/net/ipv4/ip_gre.c +++ b/net/ipv4/ip_gre.c@@ -733,7 +733,6 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, /* Push ERSPAN header */ if (tunnel->erspan_ver == 0) { proto = htons(ETH_P_ERSPAN); - __clear_bit(IP_TUNNEL_SEQ_BIT, flags); } else if (tunnel->erspan_ver == 1) { erspan_build_header(skb, ntohl(tunnel->parms.o_key), tunnel->index,@@ -748,6 +747,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, goto free_skb; } + __assign_bit(IP_TUNNEL_SEQ_BIT, flags, tunnel->erspan_ver != 0);
[Severity: Low]
Now that the SEQ bit is forced here for erspan_ver != 0 whatever
t->parms.o_flags holds, does the reported configuration still match what
goes out on the wire?
erspan_validate() only checks the union of IFLAGS and OFLAGS. So a v1/v2
device can be created or changed with IFLAGS=SEQ|KEY and OFLAGS=KEY
(or 0). p.o_flags is stored as is, and ipgre_fill_info() only adds the
KEY bit:
net/ipv4/ip_gre.c:ipgre_fill_info() {
...
if (t->erspan_ver != 0 && !t->collect_md)
__set_bit(IP_TUNNEL_KEY_BIT, o_flags);
...
}
The dumped IFLA_GRE_OFLAGS would then show output sequencing as off.
Yet every transmitted packet carries the GRE S bit and a sequence number.
quoted hunk ↗ jump to hunk
__clear_bit(IP_TUNNEL_KEY_BIT, flags); __gre_xmit(skb, dev, &tunnel->parms.iph, proto, flags); return NETDEV_TX_OK;@@ -1169,17 +1169,18 @@ static int erspan_validate(struct nlattr *tb[], struct nlattr *data[], if (ret) return ret; - if (data[IFLA_GRE_ERSPAN_VER] && - nla_get_u8(data[IFLA_GRE_ERSPAN_VER]) == 0) - return 0; - - /* ERSPAN type II/III should only have GRE sequence and key flag */ if (data[IFLA_GRE_OFLAGS]) flags |= nla_get_be16(data[IFLA_GRE_OFLAGS]); if (data[IFLA_GRE_IFLAGS]) flags |= nla_get_be16(data[IFLA_GRE_IFLAGS]); - if (!data[IFLA_GRE_COLLECT_METADATA] && - flags != (GRE_SEQ | GRE_KEY)) + + if ((data[IFLA_GRE_ERSPAN_VER] && + nla_get_u8(data[IFLA_GRE_ERSPAN_VER]) == 0) || + data[IFLA_GRE_COLLECT_METADATA]) + return flags ? -EINVAL : 0;
[Severity: Medium] This now rejects non-zero IFLA_GRE_IFLAGS/IFLA_GRE_OFLAGS for erspan_ver 0 and for IFLA_GRE_COLLECT_METADATA, on both newlink and changelink. Both cases used to be accepted: ver 0 returned early, and collect_md skipped the flags check. Is this user-visible change intended for a stable backport? The changelog lists the new rejection but doesn't say why it is needed. For collect_md, erspan_fb_xmit() never reads parms.o_flags. What does rejecting the flags there protect against? A v1/v2 device's own dump also no longer seems replayable with a different version. ipgre_fill_info() reports SEQ|KEY for v1/v2 (KEY is forced, as shown above). A request with IFLA_GRE_ERSPAN_VER=0 plus those flags now fails with -EINVAL. If a client resends the existing flags and has no way to clear SEQ, can a v1/v2 -> v0 changelink still be done, or does the device have to be recreated? I haven't checked iproute2's exact behaviour here.
quoted hunk ↗ jump to hunk
+ + /* ERSPAN type II/III should only have GRE sequence and key flag */ + if (flags != (GRE_SEQ | GRE_KEY)) return -EINVAL; /* ERSPAN Session ID only has 10-bit. Since we reuse@@ -1317,7 +1318,11 @@ static int erspan_netlink_parms(struct net_device *dev, return -EINVAL; } - if (gparms->erspan_ver == 1) { + if (gparms->erspan_ver == 0) { + if (!ip_tunnel_flags_empty(parms->i_flags) || + !ip_tunnel_flags_empty(parms->o_flags)) + return -EINVAL;
[Severity: Medium] Can a changelink on an existing erspan_ver 0 device still succeed if it doesn't resend IFLA_GRE_ERSPAN_VER? erspan_validate() has no view of the device state. Without IFLA_GRE_ERSPAN_VER (and without IFLA_GRE_COLLECT_METADATA), it skips the new early return and requires: if (flags != (GRE_SEQ | GRE_KEY)) return -EINVAL; A request with no flags attributes has flags == 0, so it fails there too. erspan_netlink_parms() then inherits gparms->erspan_ver from t->erspan_ver (0). This new branch rejects any non-empty i_flags or o_flags, which is exactly what erspan_validate() just required. So on a v0 device, no request that carries IFLA_INFO_DATA without IFLA_GRE_ERSPAN_VER seems able to pass both checks. Before this patch, the same request with SEQ|KEY was accepted. Is that intended?
+ } else if (gparms->erspan_ver == 1) {
if (data[IFLA_GRE_ERSPAN_INDEX]) {
gparms->index = nla_get_u32(data[IFLA_GRE_ERSPAN_INDEX]);
if (gparms->index & ~INDEX_MASK)[ ... ]
quoted hunk ↗ jump to hunk
@@ -1575,15 +1607,30 @@ static int erspan_changelink(struct net_device *dev, struct nlattr *tb[], if (err) return err; + link_changed = t->parms.link != p.link || t->fwmark != gparms.fwmark; + err = ip_tunnel_changelink(dev, tb, &p, gparms.fwmark); if (err < 0) - return err; + goto link_update; + + if (link_changed) + old_hlen = t->hlen; ipgre_commit_parms(t, &gparms); ip_tunnel_flags_copy(t->parms.i_flags, p.i_flags); ip_tunnel_flags_copy(t->parms.o_flags, p.o_flags); - return 0; +link_update: + /* ipgre_newlink_encap_setup() has published a new encapsulation + * without the ERSPAN header length even if ip_tunnel_changelink() + * failed, so the lengths must be refreshed on that error path too. + * + * As in ipgre_changelink(), IFLA_MTU must not hold the MTU back if we + * return an error, because do_setlink() will not apply it then. + */ + erspan_link_update(dev, err || !tb[IFLA_MTU], old_hlen);
[Severity: Medium]
Now that tun_hlen and hlen change at runtime on a live device, what
orders these updates against erspan_xmit()?
erspan_changelink() does plain stores in this order:
- t->erspan_ver, in ipgre_commit_parms()
- t->parms.o_flags
- tun_hlen and hlen, in erspan_set_hlen()
- dev->needed_headroom, in ip_tunnel_bind_dev()
There is no netif_tx_disable(), no synchronize_net() and no
READ_ONCE()/WRITE_ONCE().
erspan_xmit() runs without RTNL and reads these values one after
another:
erspan_xmit()
skb_cow_head(skb, dev->needed_headroom)
ip_tunnel_flags_copy(flags, tunnel->parms.o_flags)
tunnel->erspan_ver (if/else chain, ERSPAN header)
tunnel->erspan_ver (new __assign_bit())
__gre_xmit()
gre_build_header(skb, tunnel->tun_hlen, ...)
Each read can see a different generation of the configuration:
- 0 -> 2: SEQ is set from the new version while tun_hlen is still 4. The
sequence number then overwrites greh->flags and greh->protocol, which
is the corruption this patch fixes, now as a transient window.
- 2 -> 0: SEQ is cleared while tun_hlen is still 8. 8 bytes are pushed
but only 4 are written, so 4 bytes of stale headroom go on the wire.
- The ERSPAN header and the SEQ bit can come from different versions.
- The skb may have been cowed for the old, smaller needed_headroom while
up to 20 + 8 bytes get pushed. With an underlay whose hard_header_len
+ needed_headroom is below 4, could skb_push() reach
skb_under_panic()? That seems to need very little headroom slack, but
it would contradict the "no memory safety issue" statement.
Would it close this window to take one READ_ONCE() snapshot of
erspan_ver in erspan_xmit(), derive both the GRE length and the SEQ bit
from it, and cow for the size actually pushed? A similar o_flags vs
tun_hlen race seems to exist already for plain GRE in
ipgre_link_update().
+ + return err; }
-- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260923035217.179102-1-edumazet%40google.com