[PATCH net-next] gre: fix ERSPAN o_flags race/corruption in xmit and fill_info
From: Eric Dumazet <edumazet@google.com>
Date: 2026-06-15 14:03:36
Subsystem:
networking [general], networking [ipv4/ipv6], the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds
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 shouldn't,
leading to GRE header corruption (injecting a key field into the ERSPAN
GRE header).
Fix this by:
1) Passing flags as an argument to __gre_xmit().
2) Using local flags in erspan_xmit() and 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")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
I found this issue while working on RTNL-less fill_info().
Sent to net-next since 7.1 was just released.
net/ipv4/ip_gre.c | 30 +++++++++++++++---------------
net/ipv6/ip6_gre.c | 5 ++---
2 files changed, 17 insertions(+), 18 deletions(-)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 208dd48012d963b9df0eddbdda73dd319930e48f..eab6d228d062b97b6f3f9d03418b84bac12b6983 100644
--- a/net/ipv4/ip_gre.c
+++ b/net/ipv4/ip_gre.c@@ -475,12 +475,9 @@ static int gre_rcv(struct sk_buff *skb) static void __gre_xmit(struct sk_buff *skb, struct net_device *dev, const struct iphdr *tnl_params, - __be16 proto) + __be16 proto, const unsigned long *flags) { struct ip_tunnel *tunnel = netdev_priv(dev); - IP_TUNNEL_DECLARE_FLAGS(flags); - - ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); /* Push GRE header. */ gre_build_header(skb, tunnel->tun_hlen,
@@ -692,7 +689,7 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb, tunnel->parms.o_flags))) goto free_skb; - __gre_xmit(skb, dev, tnl_params, skb->protocol); + __gre_xmit(skb, dev, tnl_params, skb->protocol, tunnel->parms.o_flags); return NETDEV_TX_OK; free_skb:
@@ -705,6 +702,7 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); + IP_TUNNEL_DECLARE_FLAGS(flags); bool truncate = false; __be16 proto;
@@ -728,10 +726,12 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, truncate = true; } + ip_tunnel_flags_copy(flags, tunnel->parms.o_flags); + /* Push ERSPAN header */ if (tunnel->erspan_ver == 0) { proto = htons(ETH_P_ERSPAN); - __clear_bit(IP_TUNNEL_SEQ_BIT, tunnel->parms.o_flags); + __clear_bit(IP_TUNNEL_SEQ_BIT, flags); } else if (tunnel->erspan_ver == 1) { erspan_build_header(skb, ntohl(tunnel->parms.o_key), tunnel->index,
@@ -746,8 +746,8 @@ static netdev_tx_t erspan_xmit(struct sk_buff *skb, goto free_skb; } - __clear_bit(IP_TUNNEL_KEY_BIT, tunnel->parms.o_flags); - __gre_xmit(skb, dev, &tunnel->parms.iph, proto); + __clear_bit(IP_TUNNEL_KEY_BIT, flags); + __gre_xmit(skb, dev, &tunnel->parms.iph, proto, flags); return NETDEV_TX_OK; free_skb:
@@ -776,7 +776,7 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb, if (skb_cow_head(skb, dev->needed_headroom)) goto free_skb; - __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB)); + __gre_xmit(skb, dev, &tunnel->parms.iph, htons(ETH_P_TEB), tunnel->parms.o_flags); return NETDEV_TX_OK; free_skb:
@@ -1554,12 +1554,15 @@ static size_t ipgre_get_size(const struct net_device *dev) static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip_tunnel *t = netdev_priv(dev); - struct ip_tunnel_parm_kern *p = &t->parms; + const struct ip_tunnel *t = netdev_priv(dev); + const struct ip_tunnel_parm_kern *p = &t->parms; IP_TUNNEL_DECLARE_FLAGS(o_flags); ip_tunnel_flags_copy(o_flags, p->o_flags); + if (t->erspan_ver != 0 && !t->collect_md) + __set_bit(IP_TUNNEL_KEY_BIT, o_flags); + if (nla_put_u32(skb, IFLA_GRE_LINK, p->link) || nla_put_be16(skb, IFLA_GRE_IFLAGS, gre_tnl_flags_to_gre_flags(p->i_flags)) ||
@@ -1602,12 +1605,9 @@ static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev) static int erspan_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip_tunnel *t = netdev_priv(dev); + const struct ip_tunnel *t = netdev_priv(dev); if (t->erspan_ver <= 2) { - if (t->erspan_ver != 0 && !t->collect_md) - __set_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags); - if (nla_put_u8(skb, IFLA_GRE_ERSPAN_VER, t->erspan_ver)) goto nla_put_failure;
diff --git a/net/ipv6/ip6_gre.c b/net/ipv6/ip6_gre.c
index 795be59946f7210bfae55d20500d18c83c01ede9..d0701351934ccfcfbac70bb2c2c2a7ccb9b6d779 100644
--- a/net/ipv6/ip6_gre.c
+++ b/net/ipv6/ip6_gre.c@@ -964,7 +964,6 @@ static netdev_tx_t ip6erspan_tunnel_xmit(struct sk_buff *skb, if (skb_cow_head(skb, dev->needed_headroom ?: t->hlen)) goto tx_err; - __clear_bit(IP_TUNNEL_KEY_BIT, t->parms.o_flags); IPCB(skb)->flags = 0; /* For collect_md mode, derive fl6 from the tunnel key,
@@ -2112,8 +2111,8 @@ static size_t ip6gre_get_size(const struct net_device *dev) static int ip6gre_fill_info(struct sk_buff *skb, const struct net_device *dev) { - struct ip6_tnl *t = netdev_priv(dev); - struct __ip6_tnl_parm *p = &t->parms; + const struct ip6_tnl *t = netdev_priv(dev); + const struct __ip6_tnl_parm *p = &t->parms; IP_TUNNEL_DECLARE_FLAGS(o_flags); ip_tunnel_flags_copy(o_flags, p->o_flags);
--
2.54.0.1136.gdb2ca164c4-goog