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

From: Xiao Liang

Date: Tue May 19 2026 - 23:11:39 EST


On Tue, May 19, 2026 at 8:35 PM Maoyi Xie <maoyixie.tju@xxxxxxxxx> wrote:
>
> 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")

Again 5e72ce3e3980 doesn't introduce this bug.

> 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);

Also check ns_capable() against self->net?

> 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
>