* [PATCH net v3 0/2] ip6_vti: vti6_changelink and vti6_siocdevprivate netns fixes
@ 2026-05-19 12:35 Maoyi Xie
2026-05-19 12:35 ` [PATCH net v3 1/2] ip6: vti: Use ip6_tnl.net in vti6_changelink() Maoyi Xie
2026-05-19 12:35 ` [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate() Maoyi Xie
0 siblings, 2 replies; 7+ messages in thread
From: Maoyi Xie @ 2026-05-19 12:35 UTC (permalink / raw)
To: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet, David Ahern
Cc: Kuniyuki Iwashima, Steffen Klassert, Shaw Leon, netdev,
linux-kernel, stable
v2 -> v3:
- 1/2 unchanged (Reviewed-by: Eric Dumazet, carried).
- 2/2 (vti6_siocdevprivate hijack fix) is new. It closes the
regression Jakub flagged in the v2 1/2 review. PoC details
posted in the v2 thread on 2026-05-04.
- v2's 2/2 (ip6_gre: Use cached t->net in
ip6erspan_changelink()) was applied independently as commit
1d324c2f43f. It is not part of v3.
Kuniyuki Iwashima (1):
ip6: vti: Use ip6_tnl.net in vti6_changelink().
Maoyi Xie (1):
ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().
net/ipv6/ip6_vti.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v3 1/2] ip6: vti: Use ip6_tnl.net in vti6_changelink().
2026-05-19 12:35 [PATCH net v3 0/2] ip6_vti: vti6_changelink and vti6_siocdevprivate netns fixes Maoyi Xie
@ 2026-05-19 12:35 ` Maoyi Xie
2026-05-19 12:35 ` [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate() Maoyi Xie
1 sibling, 0 replies; 7+ messages in thread
From: Maoyi Xie @ 2026-05-19 12:35 UTC (permalink / raw)
To: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet, David Ahern
Cc: Kuniyuki Iwashima, Steffen Klassert, Shaw Leon, netdev,
linux-kernel, stable
From: Kuniyuki Iwashima <kuniyu@google.com>
ip netns add ns1
ip netns add ns2
ip -n ns1 link add vti6_test type vti6 remote ::1 local ::2 key 7
ip -n ns1 link set vti6_test netns ns2
ip -n ns2 link set vti6_test type vti6 remote ::3 local ::4 key 9
ip netns del ns2
ip netns del ns1
[ 132.495484] ------------[ cut here ]------------
[ 132.497609] kernel BUG at net/core/dev.c:12376!
After commit 5e72ce3e3980 ("net: ipv6: Use link netns in newlink() of
rtnl_link_ops"), vti6_newlink() correctly resolves the per-netns vti6
hash via link_net. vti6_changelink() and vti6_update() were not
converted in that series and still read dev_net(dev) /
dev_net(t->dev), which diverge from the device's creation netns
after IFLA_NET_NS_FD migration. The result is a stale per-netns hash
entry; cleanup_net() of the original netns then walks freed memory.
Reachable from an unprivileged user namespace ("unshare --user
--map-root-user --net"); cross-tenant scope on container hosts.
Fixes: 5e72ce3e3980 ("net: ipv6: Use link netns in newlink() of rtnl_link_ops")
Reported-by: Maoyi Xie <maoyi.xie@ntu.edu.sg>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Cc: stable@vger.kernel.org # v5.15+
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
net/ipv6/ip6_vti.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/net/ipv6/ip6_vti.c b/net/ipv6/ip6_vti.c
index ad5290be4..dcb257411 100644
--- a/net/ipv6/ip6_vti.c
+++ b/net/ipv6/ip6_vti.c
@@ -722,10 +722,11 @@ vti6_tnl_change(struct ip6_tnl *t, const struct __ip6_tnl_parm *p,
static int vti6_update(struct ip6_tnl *t, struct __ip6_tnl_parm *p,
bool keep_mtu)
{
- struct net *net = dev_net(t->dev);
- struct vti6_net *ip6n = net_generic(net, vti6_net_id);
+ struct net *net = t->net;
+ struct vti6_net *ip6n;
int err;
+ ip6n = net_generic(net, vti6_net_id);
vti6_tnl_unlink(ip6n, t);
synchronize_net();
err = vti6_tnl_change(t, p, keep_mtu);
@@ -1031,11 +1032,12 @@ static int vti6_changelink(struct net_device *dev, struct nlattr *tb[],
struct nlattr *data[],
struct netlink_ext_ack *extack)
{
- struct ip6_tnl *t;
+ struct ip6_tnl *t = netdev_priv(dev);
+ struct net *net = t->net;
struct __ip6_tnl_parm p;
- struct net *net = dev_net(dev);
- struct vti6_net *ip6n = net_generic(net, vti6_net_id);
+ struct vti6_net *ip6n;
+ ip6n = net_generic(net, vti6_net_id);
if (dev == ip6n->fb_tnl_dev)
return -EINVAL;
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().
2026-05-19 12:35 [PATCH net v3 0/2] ip6_vti: vti6_changelink and vti6_siocdevprivate netns fixes Maoyi Xie
2026-05-19 12:35 ` [PATCH net v3 1/2] ip6: vti: Use ip6_tnl.net in vti6_changelink() Maoyi Xie
@ 2026-05-19 12:35 ` Maoyi Xie
2026-05-20 3:10 ` Xiao Liang
1 sibling, 1 reply; 7+ messages in thread
From: Maoyi Xie @ 2026-05-19 12:35 UTC (permalink / raw)
To: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet, David Ahern
Cc: Kuniyuki Iwashima, Steffen Klassert, Shaw Leon, netdev,
linux-kernel, stable
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@kernel.org>
Cc: stable@vger.kernel.org # v5.15+
Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
---
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
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().
2026-05-19 12:35 ` [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate() Maoyi Xie
@ 2026-05-20 3:10 ` Xiao Liang
2026-05-21 12:58 ` Maoyi Xie
0 siblings, 1 reply; 7+ messages in thread
From: Xiao Liang @ 2026-05-20 3:10 UTC (permalink / raw)
To: Maoyi Xie
Cc: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet,
David Ahern, Kuniyuki Iwashima, Steffen Klassert, netdev,
linux-kernel, stable
On Tue, May 19, 2026 at 8:35 PM Maoyi Xie <maoyixie.tju@gmail.com> 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@kernel.org>
> Cc: stable@vger.kernel.org # v5.15+
> Signed-off-by: Maoyi Xie <maoyixie.tju@gmail.com>
> ---
> 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
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().
2026-05-20 3:10 ` Xiao Liang
@ 2026-05-21 12:58 ` Maoyi Xie
2026-05-22 3:17 ` Xiao Liang
0 siblings, 1 reply; 7+ messages in thread
From: Maoyi Xie @ 2026-05-21 12:58 UTC (permalink / raw)
To: Xiao Liang
Cc: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet,
David Ahern, Kuniyuki Iwashima, Steffen Klassert, netdev,
linux-kernel, stable
Hi Xiao,
Thanks for the review, and sorry about the wrong Fixes tag.
5e72ce3e3980 is not where the bug starts. The dev_net(dev)
vs t->net divergence first became reachable in commit
61220ab34948 ("vti6: Enable namespace changing"), which
dropped NETIF_F_NETNS_LOCAL and let vti6 devices move through
IFLA_NET_NS_FD. v4 will use that on both 1/2 and 2/2. Same
shape Jakub took for the sibling fix 1d324c2f43f7.
Thanks also for the ns_capable suggestion. The top of the
switch case only checks dev_net(dev)->user_ns. After migration
that is the attacker's netns. With the v3 patch the lookup
uses self->net. The else branch still sets t = self, and
vti6_update() inserts the device into the creation netns
hash. I reproduced this on a v3 kernel. An unprivileged
caller in the migrated netns picked params absent from
init_net. The SIOCCHGTUNNEL returned 0. SIOCGETTUNNEL in
init_net for those params returned the migrated device.
v4 adds ns_capable(self->net->user_ns, CAP_NET_ADMIN) before
the lookup. With that check the call returns -EPERM.
I will send v4 shortly.
Thanks,
Maoyi
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().
2026-05-21 12:58 ` Maoyi Xie
@ 2026-05-22 3:17 ` Xiao Liang
2026-05-22 11:01 ` Maoyi Xie
0 siblings, 1 reply; 7+ messages in thread
From: Xiao Liang @ 2026-05-22 3:17 UTC (permalink / raw)
To: Maoyi Xie
Cc: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet,
David Ahern, Kuniyuki Iwashima, Steffen Klassert, netdev,
linux-kernel, stable
On Thu, May 21, 2026 at 8:58 PM Maoyi Xie <maoyixie.tju@gmail.com> wrote:
>
> Hi Xiao,
>
> Thanks for the review, and sorry about the wrong Fixes tag.
> 5e72ce3e3980 is not where the bug starts. The dev_net(dev)
> vs t->net divergence first became reachable in commit
> 61220ab34948 ("vti6: Enable namespace changing"), which
> dropped NETIF_F_NETNS_LOCAL and let vti6 devices move through
> IFLA_NET_NS_FD. v4 will use that on both 1/2 and 2/2. Same
> shape Jakub took for the sibling fix 1d324c2f43f7.
>
> Thanks also for the ns_capable suggestion. The top of the
> switch case only checks dev_net(dev)->user_ns. After migration
> that is the attacker's netns. With the v3 patch the lookup
> uses self->net. The else branch still sets t = self, and
> vti6_update() inserts the device into the creation netns
> hash. I reproduced this on a v3 kernel. An unprivileged
> caller in the migrated netns picked params absent from
> init_net. The SIOCCHGTUNNEL returned 0. SIOCGETTUNNEL in
> init_net for those params returned the migrated device.
> v4 adds ns_capable(self->net->user_ns, CAP_NET_ADMIN) before
> the lookup. With that check the call returns -EPERM.
I think a similar issue also exists in the rtnetlink path.
rtnl_newlink() requires CAP_NET_ADMIN in the link netns only when
the IFLA_LINK_NETNSID attr is supplied. However, this attr is not
required when modifying tunnel parameters. As a result, a user with
capabilities only in the device netns can modify tunnel parameters in
the link netns, including endpoint addresses and keys. I'm not sure
if this behavior is expected.
>
> I will send v4 shortly.
>
> Thanks,
> Maoyi
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate().
2026-05-22 3:17 ` Xiao Liang
@ 2026-05-22 11:01 ` Maoyi Xie
0 siblings, 0 replies; 7+ messages in thread
From: Maoyi Xie @ 2026-05-22 11:01 UTC (permalink / raw)
To: Xiao Liang
Cc: Jakub Kicinski, David S . Miller, Paolo Abeni, Eric Dumazet,
David Ahern, Kuniyuki Iwashima, Steffen Klassert, netdev,
linux-kernel, stable
Hi Xiao,
You are right. I wrote a PoC and confirmed it on v4.
I used the same setup as the v4 test, just swapped the
SIOCCHGTUNNEL ioctl for `ip link set <name> type vti6 remote X
local Y` from inside the migrated netns. The ip command sends
RTM_NEWLINK with IFLA_INFO_DATA and no IFLA_LINK_NETNSID, so
rtnl_newlink() only checks the attacker's own user_ns. The
message lands in vti6_changelink(), vti6_update() inserts the
device into init_net's hash, and a SIOCGETTUNNEL in init_net for
the new params resolves to the migrated device. The primitive is
the same one v4 2/2 closes for the ioctl path. Only the entry
point differs.
For a fix I tried a small hunk in __rtnl_newlink(), before the
rtnl_changelink() dispatch. The hunk derives the link netns
through dev->rtnl_link_ops->get_link_net() when that callback
exists. If the link netns differs from tgt_net, it requires
netlink_ns_capable() against link_net->user_ns. I put it there
instead of in vti6_changelink() because the same gap applies to
other link_types with get_link_net (ipip, gre, sit, ip6_tnl),
and one site covers them all. link_types without get_link_net
would see no behaviour change.
I re-ran the PoC on v4 with that hunk applied. It returns
"Operation not permitted" and init_net's hash is unchanged.
I'd like to send this as a follow-up after v4 lands, since the
fix lives in net/core/rtnetlink.c rather than in vti6. v4 would
stay scoped to the ioctl path. Would that work for you?
Thanks again,
Maoyi
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-05-22 11:01 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-19 12:35 [PATCH net v3 0/2] ip6_vti: vti6_changelink and vti6_siocdevprivate netns fixes Maoyi Xie
2026-05-19 12:35 ` [PATCH net v3 1/2] ip6: vti: Use ip6_tnl.net in vti6_changelink() Maoyi Xie
2026-05-19 12:35 ` [PATCH net v3 2/2] ip6: vti: Use ip6_tnl.net in vti6_siocdevprivate() Maoyi Xie
2026-05-20 3:10 ` Xiao Liang
2026-05-21 12:58 ` Maoyi Xie
2026-05-22 3:17 ` Xiao Liang
2026-05-22 11:01 ` Maoyi Xie
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®