Re: [PATCH v2 net] gre: fix ERSPAN o_flags race/corruption in xmit and fill_info
From: Eric Dumazet <edumazet@google.com>
Date: 2026-07-21 15:37:23
Also in:
stable
Subsystem:
networking [general], networking [ipv4/ipv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
On Mon, Jul 20, 2026 at 3:51 PM Eric Dumazet [off-list ref] wrote:
For IPv4 ERSPAN:
In erspan_xmit(), the driver clears IP_TUNNEL_SEQ_BIT (for version 0)
and IP_TUNNEL_KEY_BIT directly in the shared tunnel->parms.o_flags
structure. Since transmit paths can run locklessly and concurrently,
this leads to a data race.
Furthermore, modifying tunnel->parms.o_flags permanently alters the
tunnel configuration. To work around this, erspan_fill_info() (which
reports config to userspace) was setting IP_TUNNEL_KEY_BIT back. If
erspan_fill_info (running under RTNL) and erspan_xmit (running locklessly)
race, erspan_xmit might see IP_TUNNEL_KEY_BIT set when it shouldnt,
leading to GRE header corruption (injecting a key field into the ERSPAN
GRE header).
Fix this by:
1) Snapshotting tunnel->parms.o_flags onto the stack in ipgre_xmit(),
gre_tap_xmit(), and __gre6_xmit(), and computing the GRE header length via
gre_calc_hlen(flags) instead of reading tunnel->tun_hlen. This eliminates
TOCTOU races between flags, offload checks, and header length.
Note: Since tunnel->tun_hlen is no longer used in fast path xmit, it could be
removed altogether in net-next.
2) Using local flags in erspan_xmit(), clearing IP_TUNNEL_CSUM_BIT and
IP_TUNNEL_KEY_BIT (and IP_TUNNEL_SEQ_BIT for v0) before passing them to
__gre_xmit().
3) Removing the racy modification of t->parms.o_flags in erspan_fill_info().
4) Forcing IP_TUNNEL_KEY_BIT in the reported flags for ERSPAN locally
in ipgre_fill_info().
For IPv6 ERSPAN:
ip6erspan_tunnel_xmit() was locklessly clearing IP_TUNNEL_KEY_BIT in
t->parms.o_flags even though it does not use these flags for building
the GRE header (it uses local flags). This permanently corrupts the
configuration and races with ip6gre_fill_info() which reads it.
Remove the redundant and racy modification.
This should remove false sharing in a fast path.
Add const qualifiers in ipgre_fill_info(), erspan_fill_info()
and ip6gre_fill_info() to clarify that these methods are not
supposed to write any live parameters.
Fixes: 84e54fe0a5ea ("gre: introduce native tunnel support for ERSPAN")
Fixes: ee496694b9ee ("ip_gre: do not report erspan version on GRE interface")
Fixes: 5a963eb61b7c ("ip6_gre: Add ERSPAN native tunnel support")
Cc: stable@vger.kernel.org
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
v2: addressed Sashiko and Ido feedback.
v1: https://lore.kernel.org/netdev/20260615140333.3161072-1-edumazet@google.com/ (local)
Sashiko said:
<quote>
Does this code generate malformed GRE headers for ERSPAN if the sequence bit
is missing from the output flags?
erspan_validate() permits configurations where the sequence bit is present in
the input flags but missing from the output flags. In that case, the snapshot
of the flags lacks IP_TUNNEL_SEQ_BIT.
After clearing IP_TUNNEL_CSUM_BIT and IP_TUNNEL_KEY_BIT, if IP_TUNNEL_SEQ_BIT
is also missing:
net/ipv4/ip_gre.c:__gre_xmit() {
...
gre_build_header(skb, gre_calc_hlen(flags),
...
}
gre_calc_hlen(flags) will evaluate to 4 bytes instead of the 8 bytes required
by the ERSPAN protocol. Before this patch, the hardcoded tunnel->tun_hlen
ensured an 8-byte header for ERSPAN.
Could this result in structurally malformed packets being transmitted?
</quote>
What a mess.
pw-bot: cr
I will squash the following:
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 442a2c9513e84e84013b4a6d447dd5e243428c5c..e102c378eeb5f0e6e9e6f710db1e05826e0fb070100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c@@ -739,11 +739,13 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, tunnel->index, truncate, true); proto = htons(ETH_P_ERSPAN); + __set_bit(IP_TUNNEL_SEQ_BIT, flags); } else if (tunnel->erspan_ver == 2) { erspan_build_header_v2(skb, ntohl(tunnel->parms.o_key), tunnel->dir, tunnel->hwid, truncate, true); proto = htons(ETH_P_ERSPAN2); + __set_bit(IP_TUNNEL_SEQ_BIT, flags); } else { goto free_skb; }