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 Cc: stable@vger.kernel.org # v5.15+ Signed-off-by: Maoyi Xie --- 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