summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKuniyuki Iwashima <kuniyu@google.com>2026-09-12 23:00:32 +0000
committerPaolo Abeni <pabeni@redhat.com>2026-09-15 13:21:33 +0200
commit6724a7baf149ee883eb0b694e8818eacb899f5d8 (patch)
tree35f59272ed1fadc497fa5acdc14b14bd7aab7919
parent6faf19060bd0123b69d7e9b04244fd577223ac3c (diff)
downloadlinux-next-6724a7baf149ee883eb0b694e8818eacb899f5d8.tar.gz
linux-next-6724a7baf149ee883eb0b694e8818eacb899f5d8.zip
ip_tunnel: Protect ip_tunnel_net.tunnels[] with mutex.
struct ip_tunnel.net is the netns where encapsulated packets flow into. struct ip_tunnel is linked to ip_tunnel_net.tunnels[] of netns. During netns dismantle or module unload, ip_tunnel_delete_net() iterates the list and queues devices for destruction regardless of the devices' netns. Thus, once RTNL is removed, the list can be modified concurrently from different netns due to device removal. Let's protect it with per-netns mutex. Note that dev_siocdevprivate() calls netdev_lock_ops() but it must be NOP for tunnel devices to avoid AB-BA deadlock. DEBUG_NET_WARN_ON_ONCE() is added to annotate the locking explicitly. Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com> Link: https://patch.msgid.link/20260912230043.2586313-7-kuniyu@google.com Reviewed-by: Ido Schimmel <idosch@nvidia.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
-rw-r--r--include/net/ip_tunnels.h1
-rw-r--r--net/ipv4/ip_tunnel.c42
2 files changed, 38 insertions, 5 deletions
diff --git a/include/net/ip_tunnels.h b/include/net/ip_tunnels.h
index a78dfbb98044..7102aa11fae2 100644
--- a/include/net/ip_tunnels.h
+++ b/include/net/ip_tunnels.h
@@ -218,6 +218,7 @@ struct ip_tunnel_net {
struct net_device *fb_tunnel_dev;
struct rtnl_link_ops *rtnl_link_ops;
struct hlist_head tunnels[IP_TNL_HASH_SIZE];
+ struct mutex tunnels_lock;
struct ip_tunnel __rcu *collect_md_tun;
int type;
};
diff --git a/net/ipv4/ip_tunnel.c b/net/ipv4/ip_tunnel.c
index d560ae9f0222..d44976395c7c 100644
--- a/net/ipv4/ip_tunnel.c
+++ b/net/ipv4/ip_tunnel.c
@@ -219,7 +219,8 @@ static struct ip_tunnel *ip_tunnel_find(struct ip_tunnel_net *itn,
ip_tunnel_flags_copy(flags, parms->i_flags);
- hlist_for_each_entry_rcu(t, head, hash_node, lockdep_rtnl_is_held()) {
+ hlist_for_each_entry_rcu(t, head, hash_node,
+ lockdep_is_held(&itn->tunnels_lock)) {
if (local == t->parms.iph.saddr &&
remote == t->parms.iph.daddr &&
link == READ_ONCE(t->parms.link) &&
@@ -892,6 +893,16 @@ static void ip_tunnel_update(struct ip_tunnel_net *itn,
netdev_state_change(dev);
}
+static void __ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
+{
+ struct ip_tunnel *tunnel = netdev_priv(dev);
+ struct ip_tunnel_net *itn;
+
+ itn = net_generic(tunnel->net, tunnel->ip_tnl_net_id);
+ ip_tunnel_del(itn, tunnel);
+ unregister_netdevice_queue(dev, head);
+}
+
int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
int cmd)
{
@@ -901,8 +912,12 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
struct net *net = t->net;
int err = 0;
+ DEBUG_NET_WARN_ON_ONCE(netdev_need_ops_lock(dev));
+
itn = net_generic(net, t->ip_tnl_net_id);
+ mutex_lock(&itn->tunnels_lock);
+
switch (cmd) {
case SIOCGETTUNNEL:
if (dev == itn->fb_tunnel_dev) {
@@ -986,7 +1001,7 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
dev = t->dev;
}
- ip_tunnel_dellink(dev, &dev_kill_list);
+ __ip_tunnel_dellink(dev, &dev_kill_list);
err = 0;
break;
@@ -995,6 +1010,8 @@ int ip_tunnel_ctl(struct net_device *dev, struct ip_tunnel_parm_kern *p,
}
done:
+ mutex_unlock(&itn->tunnels_lock);
+
unregister_netdevice_many(&dev_kill_list);
return err;
@@ -1091,8 +1108,9 @@ void ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
itn = net_generic(tunnel->net, tunnel->ip_tnl_net_id);
if (itn->fb_tunnel_dev != dev) {
- ip_tunnel_del(itn, netdev_priv(dev));
- unregister_netdevice_queue(dev, head);
+ mutex_lock(&itn->tunnels_lock);
+ __ip_tunnel_dellink(dev, head);
+ mutex_unlock(&itn->tunnels_lock);
}
}
EXPORT_SYMBOL_GPL(ip_tunnel_dellink);
@@ -1124,6 +1142,8 @@ int ip_tunnel_init_net(struct net *net, unsigned int ip_tnl_net_id,
for (i = 0; i < IP_TNL_HASH_SIZE; i++)
INIT_HLIST_HEAD(&itn->tunnels[i]);
+ mutex_init(&itn->tunnels_lock);
+
if (!ops || !net_has_fallback_tunnels(net)) {
struct ip_tunnel_net *it_init_net;
@@ -1162,6 +1182,8 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
ASSERT_RTNL_NET(net);
+ mutex_lock(&itn->tunnels_lock);
+
WRITE_ONCE(itn->fb_tunnel_dev, NULL);
for (h = 0; h < IP_TNL_HASH_SIZE; h++) {
@@ -1170,8 +1192,10 @@ void ip_tunnel_delete_net(struct net *net, unsigned int id,
struct ip_tunnel *t;
hlist_for_each_entry_safe(t, n, thead, hash_node)
- ip_tunnel_dellink(t->dev, head);
+ __ip_tunnel_dellink(t->dev, head);
}
+
+ mutex_unlock(&itn->tunnels_lock);
}
EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
@@ -1187,6 +1211,8 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
nt = netdev_priv(dev);
itn = net_generic(net, nt->ip_tnl_net_id);
+ mutex_lock(&itn->tunnels_lock);
+
if (nt->collect_md) {
if (rtnl_dereference(itn->collect_md_tun))
err = -EEXIST;
@@ -1223,6 +1249,8 @@ int ip_tunnel_newlink(struct net *net, struct net_device *dev,
ip_tunnel_add(itn, nt);
out:
+ mutex_unlock(&itn->tunnels_lock);
+
return err;
err_dev_set_mtu:
@@ -1246,6 +1274,8 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
if (dev == itn->fb_tunnel_dev)
return -EINVAL;
+ mutex_lock(&itn->tunnels_lock);
+
t = ip_tunnel_find(itn, p, dev->type);
if (t) {
@@ -1274,6 +1304,8 @@ int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
ip_tunnel_update(itn, t, dev, p, !tb[IFLA_MTU], fwmark);
out:
+ mutex_unlock(&itn->tunnels_lock);
+
return err;
}
EXPORT_SYMBOL_GPL(ip_tunnel_changelink);