[PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().

From: Maoyi Xie

Date: Tue May 19 2026 - 08:42:33 EST


After "ip6: vti: Use ip6_tnl.net in vti6_changelink()." in the same
series, vti6_update() unlinks and relinks the tunnel through t->net.
vti6_siocdevprivate() still uses dev_net(dev) for the collision
lookup. For a tunnel migrated through IFLA_NET_NS_FD, dev_net(dev)
is the new namespace, not t->net.

The SIOCCHGTUNNEL path on a migrated tunnel then proceeds as
follows:

net = dev_net(dev) /* migrated netns */
t = vti6_locate(net, &p1, false) /* misses target in t->net */
...
t = netdev_priv(dev)
vti6_update(t, &p1, false) /* mutates t->net's hash */

A caller in the migrated netns sets the migrated tunnel's parameters
to those of a tunnel that lives only in the creation netns. The
collision check in dev_net(dev) sees nothing. vti6_update() then
prepends the migrated tunnel at the head of the creation netns
hash bucket for those parameters. Subsequent lookups in the creation
netns resolve to the migrated device. xfrm receive delivers packets
matching those parameters through a device the caller controls.

Reachable from an unprivileged user namespace ("unshare --user
--map-root-user --net"). Cross tenant scope on container hosts.

Use t->net for the SIOCCHGTUNNEL path on a non fallback device. The
lookup then matches the namespace vti6_update() operates on.
SIOCADDTUNNEL and SIOCCHGTUNNEL on the fallback device retain
dev_net(dev), which equals init_net for the fallback.

Fixes: 5e72ce3e3980 ("net: ipv6: Use link netns in newlink() of rtnl_link_ops")
Suggested-by: Jakub Kicinski <kuba@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx # v5.15+
Signed-off-by: Maoyi Xie <maoyixie.tju@xxxxxxxxx>
---
net/ipv6/ip6_vti.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c
--- a/net/ipv6/ip6_vti.c
+++ b/net/ipv6/ip6_vti.c
@@ -834,15 +834,19 @@ vti6_siocdevprivate(struct net_device *dev, struct ifreq *ifr, void __user *data
if (p.proto != IPPROTO_IPV6 && p.proto != 0)
break;
vti6_parm_from_user(&p1, &p);
- t = vti6_locate(net, &p1, cmd == SIOCADDTUNNEL);
if (dev != ip6n->fb_tnl_dev && cmd == SIOCCHGTUNNEL) {
+ struct ip6_tnl *self = netdev_priv(dev);
+
+ t = vti6_locate(self->net, &p1, false);
if (t) {
if (t->dev != dev) {
err = -EEXIST;
break;
}
} else
- t = netdev_priv(dev);
+ t = self;

err = vti6_update(t, &p1, false);
+ } else {
+ t = vti6_locate(net, &p1, cmd == SIOCADDTUNNEL);
}
if (t) {
--
2.34.1