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

From: Maoyi Xie

Date: Mon May 04 2026 - 01:52:13 EST


On 4/30/26, Jakub Kicinski wrote (forwarding AI review):
> Because the collision check occurs in the new namespace (dev_net(dev)), but
> vti6_update() now modifies the original namespace's hash table (t->net),
> could an attacker in the new namespace configure their tunnel to perfectly
> match the parameters of an existing victim tunnel in the original namespace?
>
> Since the check in the new namespace finds no collision, it seems it bypasses
> the error check. Then vti6_update() prepends the attacker's tunnel
> into the original namespace's hash table, which might allow intercepting or
> hijacking traffic destined for the victim tunnel.

Confirmed empirically. PoC reproduces on a v7.0 kernel with the
posted 1/2 patch applied.

Setup:
1. Real init_net root creates a victim tunnel "vti_victim" with
laddr=fc00::1 raddr=fc00::a in init_net. An attacker tunnel
"vti_attacker" with different params (laddr=fc00::100
raddr=fc00::200) also in init_net.
2. fork() a child that unshare(CLONE_NEWUSER | CLONE_NEWNET) and
becomes "root" only in its own user_ns.
3. Real root migrates vti_attacker into the child's netns via
"ip link set vti_attacker netns <cpid>".
4. Child issues SIOCCHGTUNNEL on vti_attacker with new params equal
to vti_victim's (laddr=fc00::1 raddr=fc00::a).

Result on init_net's hash for params=fc00::1/fc00::a:

[child] SIOCCHGTUNNEL succeeded
[parent] SIOCGETTUNNEL on init_net's ip6_vti0 with
params=fc00::1/fc00::a returns name='vti_attacker'

So vti6_locate(init_net, victim_params, 0) now returns the attacker's
tunnel rather than the victim's. The mechanics match the review:

- vti6_siocdevprivate runs net = dev_net(dev) = child_netns.
- vti6_locate(child_netns, victim_params) finds nothing.
- else branch: t = netdev_priv(attacker_dev).
- vti6_update(t, victim_params) under the 1/2 patch operates on
t->net = init_net:
vti6_tnl_unlink(init_net's ip6n, t) ; t was linked there
vti6_tnl_change(t, victim_params)
vti6_tnl_link(init_net's ip6n, t) ; prepend at head
- init_net's bucket-for-victim_params chain is now
attacker (head) -> victim
- Subsequent matches in init_net resolve to the attacker.

Once an inbound xfrm packet matches victim_params in init_net, the
attacker's tunnel handles rcv/xmit, with t->dev still in the child
netns. So packets destined for the victim are delivered through
the attacker's dev in a netns the attacker fully controls.

Switching vti6_siocdevprivate() to use t->net for the collision
check (or doing the check after vti6_update() under the same lock
that vti6_update is already serialised by) closes the gap, mirroring
what 1/2 already does for vti6_changelink and vti6_update.

Happy to send a follow-up patch if you would prefer me to take it
on, or to wait for v2 of the series. Whichever works for you.

PoC source and the run output above are in poc_vti6_hijack.c and
poc_log.txt, attached.

Best regards,
Maoyi
Nanyang Technological University
https://maoyixie.com/
[*] Clean prior tunnels (best effort)
[!] cmd 'ip link del vti_victim 2>/dev/null' rc=256
[!] cmd 'ip link del vti_attacker 2>/dev/null' rc=256
[*] Create victim tunnel vti_victim with laddr=fc00::1 raddr=fc00::a
[*] Create attacker tunnel vti_attacker with laddr=fc00::100 raddr=fc00::200
[child] uid=0 netns=net:[4026532261]
[parent] migrating vti_attacker to child netns (pid=417)
[child] attacker tunnel migrated to my netns
13: vti_attacker@NONE: <POINTOPOINT,NOARP> mtu 1460 qdisc noop state DOWN mode DEFAULT group default qlen 1000
[child] SIOCCHGTUNNEL: change vti_attacker params to victim's (laddr=fc00::1 raddr=fc00::a)
[child] SIOCCHGTUNNEL succeeded

[*] Verification: SIOCGETTUNNEL in init_net on params=fc00::1/fc00::a
[parent] SIOCGETTUNNEL returned tunnel name='vti_attacker'

*** HIJACK CONFIRMED: init_net's vti6 hash for params=fc00::1/fc00::a now resolves to attacker dev 'vti_attacker' (was 'vti_victim'). Cross-netns traffic-hijack window is real. ***

Attachment: poc_vti6_hijack.c
Description: Binary data