From: Eric Dumazet <edumazet@google.com>
To: "David S . Miller" <davem@davemloft.net>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>
Cc: Simon Horman <horms@kernel.org>, Ido Schimmel <idosch@nvidia.com>,
netdev@vger.kernel.org, eric.dumazet@gmail.com,
Eric Dumazet <edumazet@google.com>
Subject: [PATCH v4 net-next] gre: fix ERSPAN o_flags race/corruption in xmit and fill_info
Date: Wed, 12 Aug 2026 14:22:57 +0000 [thread overview]
Message-ID: <20260812142257.21283-1-edumazet@google.com> (raw)
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 stack flags in ipgre_xmit(), gre_tap_xmit(), and erspan_xmit()
to prevent TOCTOU data races with concurrent configuration updates,
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>
---
v4: addressed sashiko's feedback.
v3: https://lore.kernel.org/netdev/20260811095237.2314234-1-edumazet@google.com/
v2: https://lore.kernel.org/netdev/20260720135132.3957146-1-edumazet@google.com/
net/ipv4/ip_gre.c | 42 +++++++++++++++++++++++-------------------
net/ipv6/ip6_gre.c | 5 ++---
2 files changed, 25 insertions(+), 22 deletions(-)
diff --git a/net/ipv4/ip_gre.c b/net/ipv4/ip_gre.c
index 6a92607401e8aae5a10bd758820d37f51fd7a25b..82309efd417e0f1f6554e7028be8e05d769e932d 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,
@@ -653,6 +650,7 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
+ IP_TUNNEL_DECLARE_FLAGS(flags);
const struct iphdr *tnl_params;
if (!pskb_inet_may_pull(skb))
@@ -688,11 +686,12 @@ static netdev_tx_t ipgre_xmit(struct sk_buff *skb,
tnl_params = &tunnel->parms.iph;
}
- if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
- tunnel->parms.o_flags)))
+ ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
+
+ if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags)))
goto free_skb;
- __gre_xmit(skb, dev, tnl_params, skb->protocol);
+ __gre_xmit(skb, dev, tnl_params, skb->protocol, flags);
return NETDEV_TX_OK;
free_skb:
@@ -705,6 +704,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 +728,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 +748,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:
@@ -760,6 +762,7 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
struct net_device *dev)
{
struct ip_tunnel *tunnel = netdev_priv(dev);
+ IP_TUNNEL_DECLARE_FLAGS(flags);
if (!pskb_inet_may_pull(skb))
goto free_skb;
@@ -769,14 +772,15 @@ static netdev_tx_t gre_tap_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
}
- if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT,
- tunnel->parms.o_flags)))
+ ip_tunnel_flags_copy(flags, tunnel->parms.o_flags);
+
+ if (gre_handle_offloads(skb, test_bit(IP_TUNNEL_CSUM_BIT, flags)))
goto free_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), flags);
return NETDEV_TX_OK;
free_skb:
@@ -1560,12 +1564,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)) ||
@@ -1608,12 +1615,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 b843116e9b703c701a1a28a85569501d83ede21d..0b3f386b51a2d8c97e67472bcccc15285f7fc15a 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,
@@ -2115,8 +2114,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.55.0.679.g6767b8d81c-goog
next reply other threads:[~2026-08-12 14:23 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-12 14:22 Eric Dumazet [this message]
2026-08-14 13:42 ` [PATCH v4 net-next] gre: fix ERSPAN o_flags race/corruption in xmit and fill_info Simon Horman
2026-08-14 20:10 ` patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260812142257.21283-1-edumazet@google.com \
--to=edumazet@google.com \
--cc=davem@davemloft.net \
--cc=eric.dumazet@gmail.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.